mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
feat: search
This commit is contained in:
1 parent
385cc3b6d2
commit
d6e37119a8
12 files changed
+98
-17
No files matched your search
Vendored
+1
@@ -67,6 +67,7 @@
|
|||||||
"DEBUFF",
|
"DEBUFF",
|
||||||
"debugflags",
|
"debugflags",
|
||||||
"deburr",
|
"deburr",
|
||||||
|
"deburred",
|
||||||
"decalmask",
|
"decalmask",
|
||||||
"decompressor",
|
"decompressor",
|
||||||
"décor",
|
"décor",
|
||||||
|
|||||||
@@ -121,5 +121,11 @@
|
|||||||
"no_docs": "This method has no documentation.",
|
"no_docs": "This method has no documentation.",
|
||||||
"test": "Test",
|
"test": "Test",
|
||||||
"view_full": "View full types"
|
"view_full": "View full types"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tanks": {
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search tanks..."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,13 +43,14 @@ import "@fontsource-variable/roboto-flex";
|
|||||||
--weight-bold: 700;
|
--weight-bold: 700;
|
||||||
--weight-black: 900;
|
--weight-black: 900;
|
||||||
|
|
||||||
|
--outline-interactive: var(--space-1) solid var(--gray-4);
|
||||||
--outline-focus: var(--space-1) solid var(--accent-6);
|
--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))
|
inset calc(var(--space-1) * sin(45deg)) calc(var(--space-1) * sin(45deg))
|
||||||
var(--space-1) var(--black-a3),
|
var(--space-1) var(--black-a3),
|
||||||
inset calc(var(--space-1) * sin(45deg) * -1)
|
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:
|
--shadow-bump:
|
||||||
inset calc(var(--space-1) * sin(45deg)) calc(var(--space-1) * sin(45deg))
|
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: 1.5px;
|
||||||
--shadow-outline-width-negative: calc(var(--shadow-outline-width) * -1);
|
--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);
|
var(--gray-6);
|
||||||
--shadow-interactive-shine: var(--shadow-outline-width-negative)
|
--shadow-interactive-shine: var(--shadow-outline-width-negative)
|
||||||
var(--shadow-outline-width-negative) var(--shadow-outline-width)
|
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);
|
var(--shadow-outline-width) var(--shadow-outline-width) var(--black-a4);
|
||||||
--shadow-interactive:
|
--shadow-interactive:
|
||||||
var(--shadow-interactive-shine), var(--shadow-interactive-shadow),
|
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)
|
--shadow-aggressive: var(--space-1) var(--space-1) var(--space-4)
|
||||||
var(--black-a11);
|
var(--black-a11);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: var(--page-width);
|
max-width: var(--page-width);
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
border-radius: var(--max-size);
|
border-radius: var(--max-size);
|
||||||
|
|
||||||
background-color: var(--gray-1);
|
background-color: var(--gray-1);
|
||||||
box-shadow: var(--shadow-interactive), var(--shadow-cavity);
|
outline: var(--outline-interactive);
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: var(--space-3) var(--space-4);
|
padding: var(--space-3) var(--space-4);
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { classNames } from "../../core/ui/classNames";
|
import { classNames } from "../../core/ui/classNames";
|
||||||
import "./index.css";
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
export interface TextFieldProps extends ComponentProps<"input"> {
|
export interface TextFieldProps extends ComponentProps<"input"> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
@@ -51,7 +51,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className={classNames("text-field", className)}
|
className={classNames(styles["text-field"], className)}
|
||||||
data-has-icon={children !== undefined}
|
data-has-icon={children !== undefined}
|
||||||
data-disabled={disabled}
|
data-disabled={disabled}
|
||||||
data-variant={variant}
|
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 { useMemo } from "react";
|
||||||
import { TankCard } from "../../../components/TankCard";
|
import { TankCard } from "../../../components/TankCard";
|
||||||
import { TankCards } from "../../../components/TankCards";
|
import { TankCards } from "../../../components/TankCards";
|
||||||
|
import { TankSearch } from "../../../components/TankSearch";
|
||||||
import { api } from "../../../core/api/dynamic";
|
import { api } from "../../../core/api/dynamic";
|
||||||
import { useAwait } from "../../../hooks/useAwait";
|
import { useAwait } from "../../../hooks/useAwait";
|
||||||
import { LocaleProvider } from "../../../hooks/useLocale";
|
import { LocaleProvider } from "../../../hooks/useLocale";
|
||||||
|
import { useSearchableTanks } from "../../../hooks/useSearchableTanks";
|
||||||
import { useTierCompare } from "../../../hooks/useTierCompare";
|
import { useTierCompare } from "../../../hooks/useTierCompare";
|
||||||
|
import { TankFilters } from "../../../stores/tankFilters";
|
||||||
|
|
||||||
|
const { go } = fuzzysort;
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
locale: string;
|
locale: string;
|
||||||
@@ -20,7 +26,11 @@ export function Page({ locale, ...props }: PageProps) {
|
|||||||
|
|
||||||
function Content() {
|
function Content() {
|
||||||
const tanks = useAwait(() => api.tanks(), "tanks");
|
const tanks = useAwait(() => api.tanks(), "tanks");
|
||||||
|
|
||||||
const compareTiers = useTierCompare();
|
const compareTiers = useTierCompare();
|
||||||
|
const searchableTanks = useSearchableTanks();
|
||||||
|
const search = TankFilters.use((state) => state.search);
|
||||||
|
|
||||||
const tanksOrdered = useMemo(() => {
|
const tanksOrdered = useMemo(() => {
|
||||||
let list = Object.values(tanks.tanks);
|
let list = Object.values(tanks.tanks);
|
||||||
|
|
||||||
@@ -30,12 +40,24 @@ function Content() {
|
|||||||
|
|
||||||
return list;
|
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 (
|
return (
|
||||||
<TankCards>
|
<>
|
||||||
{tanksOrdered.slice(0, 32).map(({ id }) => (
|
<TankSearch />
|
||||||
<TankCard key={id} id={id} />
|
<TankCards>
|
||||||
))}
|
{tanksFiltered.slice(0, 32).map(({ id }) => (
|
||||||
</TankCards>
|
<TankCard key={id} id={id} />
|
||||||
|
))}
|
||||||
|
</TankCards>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
import "@radix-ui/colors/violet-dark.css";
|
import "@radix-ui/colors/violet-dark.css";
|
||||||
import { Section } from "../../../components/Section";
|
import { Section } from "../../../components/Section";
|
||||||
import ToolBanner from "../../../components/ToolBanner.astro";
|
import ToolBanner from "../../../components/ToolBanner.astro";
|
||||||
import { api } from "../../../core/api/dynamic";
|
|
||||||
import { tools } from "../../../core/tools/tools";
|
import { tools } from "../../../core/tools/tools";
|
||||||
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||||
import { Page } from "./_index";
|
import { Page } from "./_index";
|
||||||
@@ -10,7 +9,6 @@ import { Page } from "./_index";
|
|||||||
export { getStaticPaths } from "../index.astro";
|
export { getStaticPaths } from "../index.astro";
|
||||||
|
|
||||||
const { locale } = Astro.props;
|
const { locale } = Astro.props;
|
||||||
const tankList = await api.tankList();
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout {locale} accent="purple">
|
<BaseLayout {locale} accent="purple">
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Varuna } from "varuna";
|
||||||
|
|
||||||
|
interface TankFilters {
|
||||||
|
search?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TankFilters = new Varuna<TankFilters>({
|
||||||
|
search: undefined,
|
||||||
|
});
|
||||||
@@ -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 { Varuna } from "varuna";
|
||||||
import { createTankState } from "../core/tankopedia/createTankState";
|
import { createTankState } from "../core/tankopedia/createTankState";
|
||||||
import { type TankState } from "../core/tankopedia/tankState";
|
import { type TankState } from "../core/tankopedia/tankState";
|
||||||
@@ -18,5 +18,5 @@ export const Tankopedia = new Varuna<Tankopedia, TankCatalogComponent>(
|
|||||||
(tank) => ({
|
(tank) => ({
|
||||||
compare: TankopediaCompare.TierAndClass,
|
compare: TankopediaCompare.TierAndClass,
|
||||||
protagonist: createTankState(tank),
|
protagonist: createTankState(tank),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
Reference in new issue
Block a user