migrate tank filters

This commit is contained in:
tresabhi committed 2026-08-26 14:02:44 -05:00
1 parent d477a3a7a4
commit 1e1040816b
13 files changed
+153 -60

No files matched your search

@@ -10,6 +10,7 @@ export interface ButtonProps extends ComponentProps<"button"> {
size?: "regular" | "minor"; size?: "regular" | "minor";
array?: boolean; array?: boolean;
parentArray?: boolean; parentArray?: boolean;
highContrast?: boolean; // TODO: add high contrast
} }
export const LIGHT_TEXT_COLORS = new Set<Color>([ export const LIGHT_TEXT_COLORS = new Set<Color>([
@@ -51,4 +52,4 @@ export function Button({
{...props} {...props}
/> />
); );
} }
@@ -0,0 +1,3 @@
.flex {
display: flex;
}
@@ -0,0 +1,20 @@
import type { ComponentProps } from "react";
import type { Space } from "../../types/space";
import { classNames } from "../../ui/classNames";
import styles from "./index.module.css";
interface Props extends ComponentProps<"div"> {
gap?: Space;
}
export function Flex({ children, className, gap, ...props }: Props) {
return (
<div
{...props}
className={classNames(styles.flex, className)}
data-gap={gap}
>
{children}
</div>
);
}
@@ -0,0 +1,5 @@
.icon-button {
padding: var(--space-3);
aspect-ratio: 1 / 1;
}
@@ -0,0 +1,12 @@
import { classNames } from "../../ui/classNames";
import { Button, type ButtonProps } from "../Button";
import styles from "./index.module.css";
export function IconButton({ className, ...props }: ButtonProps) {
return (
<Button
className={classNames(className, styles["icon-button"])}
{...props}
/>
);
}
@@ -20,7 +20,7 @@ import { App } from "../stores/app";
import { TankFilters } from "../stores/tankFilters"; import { TankFilters } from "../stores/tankFilters";
import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps"; import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps";
import { LinkI18n } from "./LinkI18n"; import { LinkI18n } from "./LinkI18n";
import { FilterControl } from "./TankSearch/components/FilterControl"; import { TankSearchFilters } from "./TankSearchFilters";
const tankDefinitions = await api.tankDefinitions(); const tankDefinitions = await api.tankDefinitions();
@@ -87,7 +87,7 @@ export function PlaylistGenerator({ skeleton }: MaybeSkeletonComponentProps) {
direction={{ initial: "column-reverse", md: "row" }} direction={{ initial: "column-reverse", md: "row" }}
> >
<Flex direction="column" gap="4" align="center"> <Flex direction="column" gap="4" align="center">
<FilterControl /> <TankSearchFilters />
<Button <Button
onClick={() => { onClick={() => {
@@ -25,12 +25,12 @@ import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonCompo
import { Callout } from "../Callout"; import { Callout } from "../Callout";
import { ExperimentIcon } from "../ExperimentIcon"; import { ExperimentIcon } from "../ExperimentIcon";
import { Link } from "../Link"; import { Link } from "../Link";
import { TankSearchBar } from "../TankSearchBar";
import { TankSearchFilters } from "../TankSearchFilters";
import { Text } from "../Text"; import { Text } from "../Text";
import { TankSearchCard } from "./components/Card"; import { TankSearchCard } from "./components/Card";
import { FilterControl } from "./components/FilterControl";
import { NoResults } from "./components/NoResults"; import { NoResults } from "./components/NoResults";
import { RecentlyViewed } from "./components/RecentlyViewed"; import { RecentlyViewed } from "./components/RecentlyViewed";
import { TankSearchBar } from "../TankSearchBar";
import { SkeletonTankCard } from "./components/SkeletonTankCard"; import { SkeletonTankCard } from "./components/SkeletonTankCard";
import { TankCardWrapper } from "./components/TankCardWrapper"; import { TankCardWrapper } from "./components/TankCardWrapper";
import { MAX_RECENTLY_VIEWED } from "./constants"; import { MAX_RECENTLY_VIEWED } from "./constants";
@@ -437,7 +437,7 @@ export const TankSearch = memo<TankSearchProps>(
onSelect={onSelect} onSelect={onSelect}
/> />
{!tankFilters.search && !tankFilters.searching && <FilterControl />} {!tankFilters.search && !tankFilters.searching && <TankSearchFilters />}
{!skeleton && !compact && <RecentlyViewed />} {!skeleton && !compact && <RecentlyViewed />}
@@ -0,0 +1,25 @@
.nation-option {
/* width="calc(100% + calc(4 * var(--space-2)))"
height="100%"
position="absolute"
right="0"
mr="-2"
style={{
backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`,
backgroundSize: "100%",
backgroundRepeat: "no-repeat",
backgroundPosition: "5rem center",
}} */
.overlay {
/* width="100%"
height="100%"
style={{
background: `linear-gradient(90deg, var(--gray-2) 50%, transparent)`,
}} */
}
}
.overflow-plus {
/* ml="1" */
}
@@ -17,18 +17,25 @@ import {
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons";
import { times } from "lodash-es"; import { times } from "lodash-es";
import { Fragment, type ComponentProps, type ReactNode } from "react"; import { Fragment, type ComponentProps, type ReactNode } from "react";
import { api } from "../../../core/blitzkit/api"; import { api } from "../../core/blitzkit/api";
import { useLocale } from "../../../hooks/useLocale"; import { useLocale } from "../../hooks/useLocale";
import { App } from "../../../stores/app"; import { App } from "../../stores/app";
import { TankFilters, type CaseType } from "../../../stores/tankFilters"; import { TankFilters, type CaseType } from "../../stores/tankFilters";
import { classIcons } from "../../ClassIcon"; import { Button } from "../Button";
import { GunAutoloaderIcon } from "../../GunAutoloaderIcon"; import { classIcons } from "../ClassIcon";
import { GunAutoreloaderIcon } from "../../GunAutoreloaderIcon"; import { DropdownMenu } from "../DropdownMenu";
import { GunRegularIcon } from "../../GunRegularIcon"; import { Flex } from "../Flex";
import { MissingShellIcon } from "../../MissingShellIcon"; import { GunAutoloaderIcon } from "../GunAutoloaderIcon";
import { ResearchedIcon } from "../../ResearchedIcon"; import { GunAutoreloaderIcon } from "../GunAutoreloaderIcon";
import { ScienceIcon } from "../../ScienceIcon"; import { GunRegularIcon } from "../GunRegularIcon";
import { ScienceOffIcon } from "../../ScienceOffIcon"; import { IconButton } from "../IconButton";
import { MissingShellIcon } from "../MissingShellIcon";
import { ResearchedIcon } from "../ResearchedIcon";
import { ScienceIcon } from "../ScienceIcon";
import { ScienceOffIcon } from "../ScienceOffIcon";
import { Text } from "../Text";
import { Tooltip } from "../Tooltip";
import styles from "./index.module.css";
const gameDefinitions = await api.gameDefinitions(); const gameDefinitions = await api.gameDefinitions();
const consumableDefinitions = await api.consumableDefinitions(); const consumableDefinitions = await api.consumableDefinitions();
@@ -165,14 +172,9 @@ const MAX_ICONS = 4;
const TIERS = times(10, (i) => 10 - i); const TIERS = times(10, (i) => 10 - i);
export function FilterControl() { export function TankSearchFilters() {
return ( return (
<Flex <div className={styles.container}>
align="center"
justify={{ initial: "start", md: "center" }}
gap="2"
wrap="wrap"
>
<TiersFilter /> <TiersFilter />
<ClassFilter /> <ClassFilter />
<TypeFilter /> <TypeFilter />
@@ -188,7 +190,7 @@ export function FilterControl() {
<GameModeAbilitiesFilter /> <GameModeAbilitiesFilter />
<ResetButton /> <ResetButton />
</Flex> </div>
); );
} }
@@ -217,13 +219,13 @@ function TiersFilter() {
<Button color="gray" variant="surface"> <Button color="gray" variant="surface">
<Flex gap="1"> <Flex gap="1">
{tiers.slice(0, MAX_ICONS).map((tier) => ( {tiers.slice(0, MAX_ICONS).map((tier) => (
<Text size="1" key={tier}> <Text size="minor" key={tier}>
{TIER_ROMAN_NUMERALS[tier]} {TIER_ROMAN_NUMERALS[tier]}
</Text> </Text>
))} ))}
{tiers.length > MAX_ICONS && ( {tiers.length > MAX_ICONS && (
<Text size="1"> <Text size="minor">
{literals(strings.common.units.plus, { {literals(strings.common.units.plus, {
value: tiers.length - MAX_ICONS, value: tiers.length - MAX_ICONS,
})} })}
@@ -330,27 +332,9 @@ function NationsFilter() {
position: "relative", position: "relative",
}} }}
> >
<Box <div className={styles["nation-option"]}>
width="calc(100% + calc(4 * var(--space-2)))" <div className={styles.overlay} />
height="100%" </div>
position="absolute"
right="0"
mr="-2"
style={{
backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`,
backgroundSize: "100%",
backgroundRepeat: "no-repeat",
backgroundPosition: "5rem center",
}}
>
<Box
width="100%"
height="100%"
style={{
background: `linear-gradient(90deg, var(--gray-2) 50%, transparent)`,
}}
/>
</Box>
<Text style={{ zIndex: 1 }}> <Text style={{ zIndex: 1 }}>
{ {
@@ -651,7 +635,6 @@ function IndividualShellFilter({
<DropdownMenu.Root modal={false}> <DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger> <DropdownMenu.Trigger>
<IconButton <IconButton
ml="-0.5px"
variant="surface" variant="surface"
style={{ style={{
borderTopLeftRadius: index === 0 ? undefined : 0, borderTopLeftRadius: index === 0 ? undefined : 0,
@@ -756,19 +739,19 @@ function OwnershipFilter() {
return wargaming ? ( return wargaming ? (
<OwnershipFilterInternal /> <OwnershipFilterInternal />
) : ( ) : (
<Tooltip content={strings.website.common.tank_search.login}> <Tooltip tooltip={strings.website.common.tank_search.login}>
<OwnershipFilterInternal /> <OwnershipFilterInternal />
</Tooltip> </Tooltip>
); );
} }
function OwnershipFilterInternal(props: FlexProps) { function OwnershipFilterInternal() {
const wargaming = App.use((state) => state.logins.wargaming); const wargaming = App.use((state) => state.logins.wargaming);
const showOwned = TankFilters.use((state) => state.showOwned); const showOwned = TankFilters.use((state) => state.showOwned);
const showUnowned = TankFilters.use((state) => state.showUnowned); const showUnowned = TankFilters.use((state) => state.showUnowned);
return ( return (
<Flex {...props}> <Flex>
<IconButton <IconButton
style={{ style={{
borderTopRightRadius: 0, borderTopRightRadius: 0,
@@ -883,7 +866,7 @@ function ConsumablesFilter() {
))} ))}
{consumables.length > MAX_ICONS && ( {consumables.length > MAX_ICONS && (
<Text size="1" ml="1"> <Text size="minor" className={styles["overflow-plus"]}>
{literals(strings.common.units.plus, { {literals(strings.common.units.plus, {
value: consumables.length - MAX_ICONS, value: consumables.length - MAX_ICONS,
})} })}
@@ -977,7 +960,7 @@ function ProvisionsFilter() {
))} ))}
{provisions.length > MAX_ICONS && ( {provisions.length > MAX_ICONS && (
<Text size="1" ml="1"> <Text size="minor" className={styles["overflow-plus"]}>
{literals(strings.common.units.plus, { {literals(strings.common.units.plus, {
value: provisions.length - MAX_ICONS, value: provisions.length - MAX_ICONS,
})} })}
@@ -1086,7 +1069,7 @@ function GameModeAbilitiesFilter() {
))} ))}
{icons.length > MAX_ICONS && ( {icons.length > MAX_ICONS && (
<Text size="1" ml="1"> <Text size="minor" className={styles["overflow-plus"]}>
{literals(strings.common.units.plus, { {literals(strings.common.units.plus, {
value: icons.length - MAX_ICONS, value: icons.length - MAX_ICONS,
})} })}
@@ -0,0 +1,28 @@
.tooltip {
position: relative;
.text {
display: none;
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
z-index: 1;
background-color: var(--gray-12);
color: var(--gray-1);
border-radius: var(--space-3);
padding: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
white-space: nowrap;
}
&:hover {
.text {
display: block;
}
}
}
@@ -0,0 +1,16 @@
import type { ReactNode } from "react";
import styles from "./index.module.css";
interface Props {
children: ReactNode;
tooltip: string;
}
export function Tooltip({ children, tooltip }: Props) {
return (
<div className={styles.tooltip}>
<div className={styles.text}>{tooltip}</div>
{children}
</div>
);
}
@@ -1,7 +1,7 @@
import { PageWrapper } from "../../../../components/PageWrapper"; import { PageWrapper } from "../../../../components/PageWrapper";
import { PlayerCountControl } from "../../../../components/Performance/PlayerCountControl"; import { PlayerCountControl } from "../../../../components/Performance/PlayerCountControl";
import { TankTable } from "../../../../components/Performance/Table"; import { TankTable } from "../../../../components/Performance/Table";
import { FilterControl } from "../../../../components/TankSearch/components/FilterControl"; import { TankSearchFilters } from "../../../../components/TankSearchFilters";
import { import {
LocaleProvider, LocaleProvider,
type LocaleAcceptorProps, type LocaleAcceptorProps,
@@ -21,7 +21,7 @@ export function Page({ skeleton, locale }: PageProps) {
function Content({ skeleton }: MaybeSkeletonComponentProps) { function Content({ skeleton }: MaybeSkeletonComponentProps) {
return ( return (
<PageWrapper pb="0" px="0" color="jade" maxWidth="100%"> <PageWrapper pb="0" px="0" color="jade" maxWidth="100%">
<FilterControl /> <TankSearchFilters />
<PlayerCountControl /> <PlayerCountControl />
<TankTable skeleton={skeleton} /> <TankTable skeleton={skeleton} />
@@ -1,5 +1,5 @@
import { PageWrapper } from "../../../../components/PageWrapper"; import { PageWrapper } from "../../../../components/PageWrapper";
import { FilterControl } from "../../../../components/TankSearch/components/FilterControl"; import { TankSearchFilters } from "../../../../components/TankSearchFilters";
import { TierListControls } from "../../../../components/TierList/Controls"; import { TierListControls } from "../../../../components/TierList/Controls";
import { TierListTable } from "../../../../components/TierList/Table"; import { TierListTable } from "../../../../components/TierList/Table";
import { TierListTiles } from "../../../../components/TierList/Tiles"; import { TierListTiles } from "../../../../components/TierList/Tiles";
@@ -23,7 +23,7 @@ function Content() {
<URLManager /> <URLManager />
<TierListControls /> <TierListControls />
<TierListTable /> <TierListTable />
<FilterControl /> <TankSearchFilters />
<TierListTiles /> <TierListTiles />
</PageWrapper> </PageWrapper>
); );