This commit is contained in:
tresabhi committed 2026-05-26 12:25:38 -05:00
1 parent 6fb51e703d
commit 26bb350709
6 files changed
+166 -95

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
{
"tabWidth": 2
}
@@ -32,6 +32,18 @@
}
}
&[data-parent-array="true"] {
*:first-child > & {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
*:last-child > & {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
svg,
img {
width: 1em;
@@ -9,6 +9,7 @@ export interface ButtonProps extends ComponentProps<"button"> {
variant?: "solid" | "surface" | "soft" | "inverted" | "ghost" | "outline";
size?: "regular" | "minor";
array?: boolean;
parentArray?: boolean;
}
export const LIGHT_TEXT_COLORS = new Set<Color>([
@@ -34,6 +35,7 @@ export function Button({
radius = "max",
size = "regular",
array = false,
parentArray = false,
...props
}: ButtonProps) {
return (
@@ -45,6 +47,7 @@ export function Button({
data-radius={radius}
data-size={size}
data-array={array}
data-parent-array={parentArray}
{...props}
/>
);
@@ -35,6 +35,8 @@ import { Section } from "../Section";
import { Text } from "../Text";
import styles from "./index.module.css";
import type { ComputedCharacteristics } from "../../tankopedia/computeCharacteristics";
import { Tooltip } from "../Tooltip";
import { useGameStrings } from "../../hooks/useGameStrings";
interface TankopediaLoadoutProps {
characteristics: ComputedCharacteristics;
@@ -178,6 +180,7 @@ function EquipmentOption({
const equipment = useEquipment(tank.tank!);
const slot = equipment.preset.slots[equipmentIndex];
const id = slot.options_catalog_i_ds[optionIndex];
const gameStrings = useGameStrings("EquipmentEntity");
const isSelected = Tankopedia.use(
(state) =>
@@ -186,25 +189,27 @@ function EquipmentOption({
);
return (
<Button
color={isSelected ? undefined : "gray"}
variant={isSelected ? "surface" : "soft"}
data-selected={isSelected}
radius="1"
array
className={styles.slot}
onClick={() => {
Tankopedia.mutate((draft) => {
if (isSelected) {
delete draft.protagonist.equipment[equipmentIndex];
} else {
draft.protagonist.equipment[equipmentIndex] = optionIndex;
}
});
}}
>
<img src={`/media/equipment/${id}.webp`} />
</Button>
<Tooltip tooltip={gameStrings[equipment.equipments[id].equipment_name]}>
<Button
color={isSelected ? undefined : "gray"}
variant={isSelected ? "surface" : "soft"}
data-selected={isSelected}
radius="1"
parentArray
className={styles.slot}
onClick={() => {
Tankopedia.mutate((draft) => {
if (isSelected) {
delete draft.protagonist.equipment[equipmentIndex];
} else {
draft.protagonist.equipment[equipmentIndex] = optionIndex;
}
});
}}
>
<img src={`/media/equipment/${id}.webp`} />
</Button>
</Tooltip>
);
}
@@ -362,6 +367,7 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
const tank = useProtagonist();
const strings = useStrings();
const gameStrings = useGameStrings("TankEntity");
const upgradePreset = useUpgradePreset(tank.tank!.tank_upgrade_preset);
let price: StandardPrice | undefined;
@@ -405,93 +411,94 @@ function LineElement({ index, lineName, stage }: LineElementProps) {
}
return (
<div className={styles.wrapper}>
<Button
className={styles.stage}
color={isSelected ? undefined : "gray"}
variant={isSelected ? "surface" : "soft"}
radius="1"
onClick={() => {
// TODO: make minimum selectable upgrade the last free module for tanks like the destiny
<Tooltip tooltip={gameStrings[stage.display_name]}>
<div className={styles.wrapper}>
<Button
className={styles.stage}
color={isSelected ? undefined : "gray"}
variant={isSelected ? "surface" : "soft"}
radius="1"
onClick={() => {
// TODO: make minimum selectable upgrade the last free module for tanks like the destiny
Tankopedia.mutate((draft) => {
const tankData = tank.tank!;
Tankopedia.mutate((draft) => {
const tankData = tank.tank!;
if (isAlternativeLine(lineName)) {
const originalLine = originalLineName(lineName)!;
if (isAlternativeLine(lineName)) {
const originalLine = originalLineName(lineName)!;
draft.protagonist.alternates[originalLine] = true;
draft.protagonist.upgrades[lineName] = index;
} else {
draft.protagonist.alternates[lineName] = false;
draft.protagonist.upgrades[lineName] = index;
}
for (const required of stage.required_upgrades) {
for (const line of tankData.upgrade_lines) {
let i = 0;
for (const candidateStage of line.stages) {
if (candidateStage.tech_name === required) {
draft.protagonist.upgrades[line.name] = Math.max(
draft.protagonist.upgrades[line.name],
i,
);
}
i++;
}
}
}
for (const line of tankData.upgrade_lines) {
if (draft.protagonist.alternates[line.name]) {
continue;
draft.protagonist.alternates[originalLine] = true;
draft.protagonist.upgrades[lineName] = index;
} else {
draft.protagonist.alternates[lineName] = false;
draft.protagonist.upgrades[lineName] = index;
}
let i = draft.protagonist.upgrades[line.name];
for (const required of stage.required_upgrades) {
for (const line of tankData.upgrade_lines) {
let i = 0;
while (i > 0) {
const currentStage = line.stages[i];
const valid = currentStage.required_upgrades.every(
(required) => {
for (const otherLine of tankData.upgrade_lines) {
const idx = draft.protagonist.upgrades[otherLine.name];
if (otherLine.stages[idx]?.tech_name === required) {
return true;
}
for (const candidateStage of line.stages) {
if (candidateStage.tech_name === required) {
draft.protagonist.upgrades[line.name] = Math.max(
draft.protagonist.upgrades[line.name],
i,
);
}
return false;
},
);
if (valid) break;
i--;
i++;
}
}
}
draft.protagonist.upgrades[line.name] = i;
}
});
}}
>
<img
className={styles.icon}
src={`/media/modules/${stage.stage_type}.webp`}
/>
{/* {stage.display_name} */}
for (const line of tankData.upgrade_lines) {
if (draft.protagonist.alternates[line.name]) {
continue;
}
<Text weight="light" lowContrast size="minor" className={styles.tier}>
{isAlternativeLine(lineName)
? strings.tanks.loadout.alternative
: romanize(stage.number)}
</Text>
</Button>
let i = draft.protagonist.upgrades[line.name];
{<Price className={styles.price} price={price} />}
</div>
while (i > 0) {
const currentStage = line.stages[i];
const valid = currentStage.required_upgrades.every(
(required) => {
for (const otherLine of tankData.upgrade_lines) {
const idx = draft.protagonist.upgrades[otherLine.name];
if (otherLine.stages[idx]?.tech_name === required) {
return true;
}
}
return false;
},
);
if (valid) break;
i--;
}
draft.protagonist.upgrades[line.name] = i;
}
});
}}
>
<img
className={styles.icon}
src={`/media/modules/${stage.stage_type}.webp`}
/>
<Text weight="light" lowContrast size="minor" className={styles.tier}>
{isAlternativeLine(lineName)
? strings.tanks.loadout.alternative
: romanize(stage.number)}
</Text>
</Button>
{<Price className={styles.price} price={price} />}
</div>
</Tooltip>
);
}
@@ -0,0 +1,30 @@
.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,
&:active,
&:focus-within {
.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>
);
}