From 7d3f32003c0afaf4eb1fb5b15f0fd31926d55fca Mon Sep 17 00:00:00 2001
From: Kalle <38327916+Sendouc@users.noreply.github.com>
Date: Sat, 27 Jun 2026 09:54:11 +0300
Subject: [PATCH] Tier list maker click to place mode
---
.../components/ItemPool.module.css | 18 ++++
.../tier-list-maker/components/ItemPool.tsx | 44 ++++++++-
.../components/TierRow.module.css | 15 +++
.../tier-list-maker/components/TierRow.tsx | 28 +++++-
.../tier-list-maker/hooks/useTierList.ts | 32 ++++++-
.../routes/tier-list-maker.tsx | 28 +++++-
.../tier-list-maker-utils.test.ts | 93 +++++++++++++++++++
.../tier-list-maker/tier-list-maker-utils.ts | 24 +++++
locales/da/tier-list-maker.json | 3 +
locales/de/tier-list-maker.json | 3 +
locales/en/tier-list-maker.json | 3 +
locales/es-ES/tier-list-maker.json | 3 +
locales/es-US/tier-list-maker.json | 3 +
locales/fr-CA/tier-list-maker.json | 3 +
locales/fr-EU/tier-list-maker.json | 3 +
locales/he/tier-list-maker.json | 3 +
locales/it/tier-list-maker.json | 3 +
locales/ja/tier-list-maker.json | 3 +
locales/ko/tier-list-maker.json | 3 +
locales/nl/tier-list-maker.json | 3 +
locales/pl/tier-list-maker.json | 3 +
locales/pt-BR/tier-list-maker.json | 3 +
locales/ru/tier-list-maker.json | 3 +
locales/zh/tier-list-maker.json | 3 +
24 files changed, 323 insertions(+), 7 deletions(-)
create mode 100644 app/features/tier-list-maker/tier-list-maker-utils.test.ts
diff --git a/app/features/tier-list-maker/components/ItemPool.module.css b/app/features/tier-list-maker/components/ItemPool.module.css
index 34f66af1b..3bea2ea55 100644
--- a/app/features/tier-list-maker/components/ItemPool.module.css
+++ b/app/features/tier-list-maker/components/ItemPool.module.css
@@ -3,3 +3,21 @@
flex-wrap: wrap;
gap: var(--s-3);
}
+
+.clickableItem {
+ min-height: 50px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ transition: transform 0.1s;
+
+ &:hover:not(:disabled) {
+ transform: scale(1.1);
+ }
+
+ &:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+}
diff --git a/app/features/tier-list-maker/components/ItemPool.tsx b/app/features/tier-list-maker/components/ItemPool.tsx
index 6170f7ee2..f815b0b45 100644
--- a/app/features/tier-list-maker/components/ItemPool.tsx
+++ b/app/features/tier-list-maker/components/ItemPool.tsx
@@ -1,19 +1,55 @@
import { useDroppable } from "@dnd-kit/core";
import { useTierListState } from "../contexts/TierListContext";
+import type { TierListItem } from "../tier-list-maker-schemas";
+import { tierListItemId } from "../tier-list-maker-utils";
import { DraggableItem } from "./DraggableItem";
import styles from "./ItemPool.module.css";
+import { TierListItemImage } from "./TierListItemImage";
export function ItemPool() {
- const { availableItems } = useTierListState();
+ const { availableItems, placementMode, selectedTierId, handleAddItemToTier } =
+ useTierListState();
const { setNodeRef } = useDroppable({
id: "item-pool",
});
return (
+
+
+ {PLACEMENT_MODES.map((mode) => (
+
+ setPlacementMode(value as TierListPlacementMode)
+ }
+ >
+ {t(
+ `tier-list-maker:placementMode${mode === "track" ? "Track" : "Click"}`,
+ )}
+
+ ))}
+
= {},
+): TierListState {
+ return {
+ tiers: [
+ { id: "tier-a", name: "A", color: "#ffffff" },
+ { id: "tier-b", name: "B", color: "#000000" },
+ ],
+ tierItems: new Map(Object.entries(tierItems)),
+ };
+}
+
+const splattershot: TierListItem = { type: "main-weapon", id: 40 };
+const splatRoller: TierListItem = { type: "main-weapon", id: 1010 };
+
+describe("addItemToTier", () => {
+ it("appends the item to the target tier", () => {
+ const state = makeState();
+
+ const result = addItemToTier(state, "tier-a", splattershot);
+
+ expect(result.tierItems.get("tier-a")).toEqual([splattershot]);
+ });
+
+ it("appends to the end keeping existing items", () => {
+ const state = makeState({ "tier-a": [splattershot] });
+
+ const result = addItemToTier(state, "tier-a", splatRoller);
+
+ expect(result.tierItems.get("tier-a")).toEqual([splattershot, splatRoller]);
+ });
+
+ it("does not mutate the original state", () => {
+ const state = makeState({ "tier-a": [splattershot] });
+
+ addItemToTier(state, "tier-a", splatRoller);
+
+ expect(state.tierItems.get("tier-a")).toEqual([splattershot]);
+ });
+
+ it("leaves other tiers untouched", () => {
+ const state = makeState({ "tier-b": [splatRoller] });
+
+ const result = addItemToTier(state, "tier-a", splattershot);
+
+ expect(result.tierItems.get("tier-b")).toEqual([splatRoller]);
+ });
+
+ it("returns the same state reference when the tier does not exist", () => {
+ const state = makeState();
+
+ const result = addItemToTier(state, "tier-missing", splattershot);
+
+ expect(result).toBe(state);
+ });
+});
+
+describe("getNextNthForItem", () => {
+ it("returns 1 when the item is not yet placed", () => {
+ const state = makeState();
+
+ expect(getNextNthForItem(splattershot, state)).toBe(1);
+ });
+
+ it("returns max nth + 1 across all tiers", () => {
+ const state = makeState({
+ "tier-a": [splattershot],
+ "tier-b": [{ ...splattershot, nth: 2 }],
+ });
+
+ expect(getNextNthForItem(splattershot, state)).toBe(3);
+ });
+});
+
+describe("tierListItemId", () => {
+ it("omits nth when it is not set", () => {
+ expect(tierListItemId(splattershot)).toBe("main-weapon:40");
+ });
+
+ it("includes nth when set", () => {
+ expect(tierListItemId({ ...splattershot, nth: 2 })).toBe(
+ "main-weapon:40:2",
+ );
+ });
+});
diff --git a/app/features/tier-list-maker/tier-list-maker-utils.ts b/app/features/tier-list-maker/tier-list-maker-utils.ts
index 25d5b0a8a..e56babbea 100644
--- a/app/features/tier-list-maker/tier-list-maker-utils.ts
+++ b/app/features/tier-list-maker/tier-list-maker-utils.ts
@@ -4,6 +4,30 @@ export function tierListItemId(item: TierListItem) {
return `${item.type}:${item.id}${item.nth ? `:${item.nth}` : ""}`;
}
+/**
+ * Returns a new tier list state with the given item appended to the end of the
+ * specified tier. Used by the "click" placement mode. If the tier does not
+ * exist the state is returned unchanged.
+ */
+export function addItemToTier(
+ state: TierListState,
+ tierId: string,
+ item: TierListItem,
+): TierListState {
+ if (!state.tiers.some((tier) => tier.id === tierId)) {
+ return state;
+ }
+
+ const newTierItems = new Map(state.tierItems);
+ const tierItems = newTierItems.get(tierId) ?? [];
+ newTierItems.set(tierId, [...tierItems, item]);
+
+ return {
+ ...state,
+ tierItems: newTierItems,
+ };
+}
+
/**
* Finds the next nth value for a duplicate item in the tier list.
* Searches through all tiers to find the maximum nth value for items
diff --git a/locales/da/tier-list-maker.json b/locales/da/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/da/tier-list-maker.json
+++ b/locales/da/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/de/tier-list-maker.json b/locales/de/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/de/tier-list-maker.json
+++ b/locales/de/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/en/tier-list-maker.json b/locales/en/tier-list-maker.json
index 2de1cc94f..7cdbf723a 100644
--- a/locales/en/tier-list-maker.json
+++ b/locales/en/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "Stage + Modes",
"abilities": "Abilities",
"dropItems": "Drop items here",
+ "clickToAdd": "Click items to add here",
+ "placementModeTrack": "Drag & drop",
+ "placementModeClick": "Click to place",
"editingTier": "Editing tier",
"hideAltKits": "Hide alt kits",
"hideAltSkins": "Hide alt skins",
diff --git a/locales/es-ES/tier-list-maker.json b/locales/es-ES/tier-list-maker.json
index 486c451c4..44bdc6d61 100644
--- a/locales/es-ES/tier-list-maker.json
+++ b/locales/es-ES/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "Escenario + Modos",
"abilities": "",
"dropItems": "Suelta los elementos aquí",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "Editando tier",
"hideAltKits": "Ocultar kits alternativos",
"hideAltSkins": "Ocultar skins alternativas",
diff --git a/locales/es-US/tier-list-maker.json b/locales/es-US/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/es-US/tier-list-maker.json
+++ b/locales/es-US/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/fr-CA/tier-list-maker.json b/locales/fr-CA/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/fr-CA/tier-list-maker.json
+++ b/locales/fr-CA/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/fr-EU/tier-list-maker.json b/locales/fr-EU/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/fr-EU/tier-list-maker.json
+++ b/locales/fr-EU/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/he/tier-list-maker.json b/locales/he/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/he/tier-list-maker.json
+++ b/locales/he/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/it/tier-list-maker.json b/locales/it/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/it/tier-list-maker.json
+++ b/locales/it/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/ja/tier-list-maker.json b/locales/ja/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/ja/tier-list-maker.json
+++ b/locales/ja/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/ko/tier-list-maker.json b/locales/ko/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/ko/tier-list-maker.json
+++ b/locales/ko/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/nl/tier-list-maker.json b/locales/nl/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/nl/tier-list-maker.json
+++ b/locales/nl/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/pl/tier-list-maker.json b/locales/pl/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/pl/tier-list-maker.json
+++ b/locales/pl/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/pt-BR/tier-list-maker.json b/locales/pt-BR/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/pt-BR/tier-list-maker.json
+++ b/locales/pt-BR/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/ru/tier-list-maker.json b/locales/ru/tier-list-maker.json
index e2bee98ce..cca1a4fbf 100644
--- a/locales/ru/tier-list-maker.json
+++ b/locales/ru/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "",
"abilities": "",
"dropItems": "",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "",
"hideAltKits": "",
"hideAltSkins": "",
diff --git a/locales/zh/tier-list-maker.json b/locales/zh/tier-list-maker.json
index 5bf21fd67..acc25f8df 100644
--- a/locales/zh/tier-list-maker.json
+++ b/locales/zh/tier-list-maker.json
@@ -9,6 +9,9 @@
"stageModes": "场地 + 模式",
"abilities": "装备能力",
"dropItems": "将项目拖拽至此处",
+ "clickToAdd": "",
+ "placementModeTrack": "",
+ "placementModeClick": "",
"editingTier": "正在编辑分级",
"hideAltKits": "隐藏贴牌武器",
"hideAltSkins": "隐藏异色武器",