feat: search

This commit is contained in:
TrèsAbhi committed 2026-04-27 19:57:44 -05:00
1 parent 385cc3b6d2
commit d6e37119a8
12 files changed
+98 -17

No files matched your search

+1
View File
@@ -67,6 +67,7 @@
"DEBUFF",
"debugflags",
"deburr",
"deburred",
"decalmask",
"decompressor",
"décor",
+6
View File
@@ -121,5 +121,11 @@
"no_docs": "This method has no documentation.",
"test": "Test",
"view_full": "View full types"
},
"tanks": {
"search": {
"placeholder": "Search tanks..."
}
}
}
@@ -43,13 +43,14 @@ import "@fontsource-variable/roboto-flex";
--weight-bold: 700;
--weight-black: 900;
--outline-interactive: var(--space-1) solid var(--gray-4);
--outline-focus: var(--space-1) solid var(--accent-6);
--shadow-cavity:
/* --shadow-cavity:
inset calc(var(--space-1) * sin(45deg)) calc(var(--space-1) * sin(45deg))
var(--space-1) var(--black-a3),
inset calc(var(--space-1) * sin(45deg) * -1)
calc(var(--space-1) * sin(45deg) * -1) var(--space-1) var(--white-a1);
calc(var(--space-1) * sin(45deg) * -1) var(--space-1) var(--white-a1); */
--shadow-bump:
inset calc(var(--space-1) * sin(45deg)) calc(var(--space-1) * sin(45deg))
@@ -60,7 +61,7 @@ import "@fontsource-variable/roboto-flex";
--shadow-outline-width: 1.5px;
--shadow-outline-width-negative: calc(var(--shadow-outline-width) * -1);
--shadow-interactive-background: 0 0 0 var(--shadow-outline-width)
/* --shadow-interactive-background: 0 0 0 var(--shadow-outline-width)
var(--gray-6);
--shadow-interactive-shine: var(--shadow-outline-width-negative)
var(--shadow-outline-width-negative) var(--shadow-outline-width)
@@ -69,7 +70,7 @@ import "@fontsource-variable/roboto-flex";
var(--shadow-outline-width) var(--shadow-outline-width) var(--black-a4);
--shadow-interactive:
var(--shadow-interactive-shine), var(--shadow-interactive-shadow),
var(--shadow-interactive-background);
var(--shadow-interactive-background); */
--shadow-aggressive: var(--space-1) var(--space-1) var(--space-4)
var(--black-a11);
@@ -6,7 +6,6 @@
.content {
flex: 1;
max-width: var(--page-width);
overflow: hidden;
display: flex;
flex-direction: column;
@@ -0,0 +1,28 @@
import { useStrings } from "../hooks/useStrings";
import { TankFilters } from "../stores/tankFilters";
import { TextField } from "./TextField";
export function TankSearch() {
const strings = useStrings();
return (
<TextField
placeholder={strings.tanks.search.placeholder}
onChange={(event) => {
const trimmed = event.target.value.trim();
if (trimmed.length === 0) {
TankFilters.mutate((draft) => {
draft.search = undefined;
});
return;
}
TankFilters.mutate((draft) => {
draft.search = trimmed;
});
}}
/>
);
}
@@ -14,7 +14,7 @@
border-radius: var(--max-size);
background-color: var(--gray-1);
box-shadow: var(--shadow-interactive), var(--shadow-cavity);
outline: var(--outline-interactive);
input {
padding: var(--space-3) var(--space-4);
@@ -8,7 +8,7 @@ import {
type ReactNode,
} from "react";
import { classNames } from "../../core/ui/classNames";
import "./index.css";
import styles from "./index.module.css";
export interface TextFieldProps extends ComponentProps<"input"> {
children?: ReactNode;
@@ -51,7 +51,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
return (
<div
{...props}
className={classNames("text-field", className)}
className={classNames(styles["text-field"], className)}
data-has-icon={children !== undefined}
data-disabled={disabled}
data-variant={variant}
@@ -0,0 +1,17 @@
import { deburr } from "lodash-es";
import { api } from "../core/api/dynamic";
import { useAwait } from "./useAwait";
import { useGameStrings } from "./useGameStrings";
export function useSearchableTanks() {
const gameStrings = useGameStrings("TankEntity");
const tanks = useAwait(() => api.tanks(), "tanks");
return Object.values(tanks.tanks).map((tank) => {
const { id } = tank;
const name = gameStrings[tank.tank!.name!.value];
const deburred = deburr(name);
return { id, name, deburred };
});
}
@@ -1,10 +1,16 @@
import fuzzysort from "fuzzysort";
import { useMemo } from "react";
import { TankCard } from "../../../components/TankCard";
import { TankCards } from "../../../components/TankCards";
import { TankSearch } from "../../../components/TankSearch";
import { api } from "../../../core/api/dynamic";
import { useAwait } from "../../../hooks/useAwait";
import { LocaleProvider } from "../../../hooks/useLocale";
import { useSearchableTanks } from "../../../hooks/useSearchableTanks";
import { useTierCompare } from "../../../hooks/useTierCompare";
import { TankFilters } from "../../../stores/tankFilters";
const { go } = fuzzysort;
interface PageProps {
locale: string;
@@ -20,7 +26,11 @@ export function Page({ locale, ...props }: PageProps) {
function Content() {
const tanks = useAwait(() => api.tanks(), "tanks");
const compareTiers = useTierCompare();
const searchableTanks = useSearchableTanks();
const search = TankFilters.use((state) => state.search);
const tanksOrdered = useMemo(() => {
let list = Object.values(tanks.tanks);
@@ -30,12 +40,24 @@ function Content() {
return list;
}, []);
const tanksFiltered = useMemo(() => {
if (!search) return tanksOrdered;
const results = go(search, searchableTanks, {
keys: ["id", "name", "deburred"],
});
return results.map(({ obj }) => obj);
}, [search]);
return (
<TankCards>
{tanksOrdered.slice(0, 32).map(({ id }) => (
<TankCard key={id} id={id} />
))}
</TankCards>
<>
<TankSearch />
<TankCards>
{tanksFiltered.slice(0, 32).map(({ id }) => (
<TankCard key={id} id={id} />
))}
</TankCards>
</>
);
}
@@ -2,7 +2,6 @@
import "@radix-ui/colors/violet-dark.css";
import { Section } from "../../../components/Section";
import ToolBanner from "../../../components/ToolBanner.astro";
import { api } from "../../../core/api/dynamic";
import { tools } from "../../../core/tools/tools";
import BaseLayout from "../../../layouts/BaseLayout.astro";
import { Page } from "./_index";
@@ -10,7 +9,6 @@ import { Page } from "./_index";
export { getStaticPaths } from "../index.astro";
const { locale } = Astro.props;
const tankList = await api.tankList();
---
<BaseLayout {locale} accent="purple">
@@ -0,0 +1,9 @@
import { Varuna } from "varuna";
interface TankFilters {
search?: string;
}
export const TankFilters = new Varuna<TankFilters>({
search: undefined,
});
+2 -2
View File
@@ -1,4 +1,4 @@
import type { TankCatalogComponent } from "@protos/blitz_static_tank_component";
import type { TankCatalogComponent } from "@protos/game/proto/legacy/blitz_static_tank_component";
import { Varuna } from "varuna";
import { createTankState } from "../core/tankopedia/createTankState";
import { type TankState } from "../core/tankopedia/tankState";
@@ -18,5 +18,5 @@ export const Tankopedia = new Varuna<Tankopedia, TankCatalogComponent>(
(tank) => ({
compare: TankopediaCompare.TierAndClass,
protagonist: createTankState(tank),
})
}),
);