User builds page dropdown weapon filter Closes #1427

This commit is contained in:
Kalle 2024-06-27 11:50:01 +03:00
parent 9d9d2ae887
commit 2eb7c9505a
5 changed files with 75 additions and 30 deletions

View File

@ -2,22 +2,22 @@ import { Menu as HeadlessUIMenu, Transition } from "@headlessui/react";
import clsx from "clsx";
import * as React from "react";
export function Menu({
button,
items,
className,
}: {
export interface MenuProps {
button: React.ElementType;
items: {
// type: "button"; TODO: type: "link"
text: string;
id: string;
id: string | number;
icon?: React.ReactNode;
onClick: () => void;
disabled?: boolean;
selected?: boolean;
}[];
className?: string;
}) {
scrolling?: boolean;
}
export function Menu({ button, items, className, scrolling }: MenuProps) {
return (
<HeadlessUIMenu as="div" className={clsx("menu-container", className)}>
<HeadlessUIMenu.Button as={button} />
@ -30,7 +30,11 @@ export function Menu({
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<HeadlessUIMenu.Items className="menu__items-container">
<HeadlessUIMenu.Items
className={clsx("menu__items-container", {
"menu-container__scrolling": scrolling,
})}
>
{items.map((item) => {
return (
<HeadlessUIMenu.Item key={item.id} disabled={item.disabled}>
@ -39,6 +43,7 @@ export function Menu({
className={clsx("menu__item", {
menu__item__active: active,
menu__item__disabled: item.disabled,
menu__item__selected: item.selected,
})}
onClick={item.onClick}
data-testid={`menu-item-${item.id}`}

View File

@ -5,13 +5,15 @@ import { BuildCard } from "~/components/BuildCard";
import { Button, LinkButton } from "~/components/Button";
import { Dialog } from "~/components/Dialog";
import { FormMessage } from "~/components/FormMessage";
import { WeaponImage } from "~/components/Image";
import { Image, WeaponImage } from "~/components/Image";
import { Menu, type MenuProps } from "~/components/Menu";
import { Popover } from "~/components/Popover";
import { SubmitButton } from "~/components/SubmitButton";
import { LockIcon } from "~/components/icons/Lock";
import { PlusIcon } from "~/components/icons/Plus";
import { SortIcon } from "~/components/icons/Sort";
import { TrashIcon } from "~/components/icons/Trash";
import { UnlockIcon } from "~/components/icons/Unlock";
import { BUILD } from "~/constants";
import { BUILD_SORT_IDENTIFIERS, type BuildSort } from "~/db/tables";
import { useUser } from "~/features/auth/core/user";
@ -20,7 +22,7 @@ import type { MainWeaponId } from "~/modules/in-game-lists";
import { mainWeaponIds } from "~/modules/in-game-lists";
import { atOrError } from "~/utils/arrays";
import type { SendouRouteHandle } from "~/utils/remix";
import { userNewBuildPage } from "~/utils/urls";
import { userNewBuildPage, weaponCategoryUrl } from "~/utils/urls";
import { DEFAULT_BUILD_SORT } from "../user-page-constants";
import type { UserPageLoaderData } from "./u.$identifier";
@ -153,6 +155,42 @@ function BuildsFilters({
const showPublicPrivateFilters =
user?.id === parentPageData.id && privateBuildsCount > 0;
const WeaponFilterMenuButton = React.forwardRef((props, ref) => (
<Button
variant={typeof weaponFilter === "number" ? undefined : "outlined"}
size="tiny"
className="u__build-filter-button"
{...props}
_ref={ref}
>
<Image
path={weaponCategoryUrl("SHOOTERS")}
width={24}
height={24}
alt=""
/>
{t("builds:filters.filterByWeapon")}
</Button>
));
const weaponFilterMenuItems = mainWeaponIds
.map((weaponId) => {
const count = data.weaponCounts[weaponId];
if (!count) return null;
const item: MenuProps["items"][number] = {
id: weaponId,
text: `${t(`weapons:MAIN_${weaponId}`)} (${count})`,
icon: <WeaponImage weaponSplId={weaponId} variant="build" size={18} />,
onClick: () => setWeaponFilter(weaponId),
selected: weaponFilter === weaponId,
};
return item;
})
.filter((item) => item !== null);
return (
<div className="stack horizontal sm flex-wrap">
<Button
@ -170,6 +208,7 @@ function BuildsFilters({
variant={weaponFilter === "PUBLIC" ? undefined : "outlined"}
size="tiny"
className="u__build-filter-button"
icon={<UnlockIcon />}
>
{t("builds:stats.public")} ({publicBuildsCount})
</Button>
@ -185,24 +224,11 @@ function BuildsFilters({
</>
) : null}
{mainWeaponIds.map((weaponId) => {
const count = data.weaponCounts[weaponId];
if (!count) return null;
return (
<Button
key={weaponId}
onClick={() => setWeaponFilter(weaponId)}
variant={weaponFilter === weaponId ? undefined : "outlined"}
size="tiny"
className="u__build-filter-button"
>
<WeaponImage weaponSplId={weaponId} variant="build" width={20} />
{t(`weapons:MAIN_${weaponId}`)} ({count})
</Button>
);
})}
<Menu
items={weaponFilterMenuItems}
button={WeaponFilterMenuButton}
scrolling
/>
</div>
);
}

View File

@ -1343,6 +1343,14 @@ dialog::backdrop {
align-items: flex-start;
}
.menu-container__scrolling {
max-height: 300px;
overflow-y: auto;
scrollbar-color: rgb(83 65 91) transparent;
scrollbar-width: thin;
scrollbar-gutter: stable;
}
.menu__item {
display: flex;
font-size: var(--fonts-xs);
@ -1374,6 +1382,11 @@ dialog::backdrop {
cursor: not-allowed;
}
.menu__item__selected {
background-color: var(--theme-transparent);
font-weight: var(--extra-bold);
}
.menu__item__icon {
width: 18px;
}

View File

@ -252,7 +252,7 @@
.u__build-filter-button {
padding: 0 var(--s-1-5) !important;
gap: var(--s-1);
min-height: 24px;
min-height: 32px;
}
.u__placements {

View File

@ -41,5 +41,6 @@
"filters.atLeast": "At least",
"filters.atMost": "At most",
"filters.date.since": "Since",
"filters.date.custom": "Custom"
"filters.date.custom": "Custom",
"filters.filterByWeapon": "Filter by weapon"
}