Files
blitzkit/packages/website/src/components/Compare/CompareRow.tsx
T

172 lines
5.4 KiB
TypeScript
Raw Normal View History

2024-07-12 20:38:19 -05:00
import {
CaretLeftIcon,
CaretRightIcon,
CaretSortIcon,
2025-09-01 23:05:22 -05:00
} from "@radix-ui/react-icons";
import { Flex, IconButton, Table, Text } from "@radix-ui/themes";
import { useRef } from "react";
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
import { useLocale } from "../../hooks/useLocale";
import { CompareEphemeral } from "../../stores/compareEphemeral";
import { ComparePersistent } from "../../stores/comparePersistent";
2025-10-26 22:01:42 -05:00
import { CompareCell, CompareCellDirection } from "../CompareCell";
2025-09-01 23:05:22 -05:00
import { StickyRowHeaderCell } from "../StickyRowHeaderCell";
2024-07-12 20:38:19 -05:00
2025-01-16 20:14:34 -06:00
type CompareRowProps = {
2024-07-12 20:38:19 -05:00
display?: (
2025-09-01 23:05:22 -05:00
member: Awaited<TankCharacteristics>
2024-07-12 20:38:19 -05:00
) => number | string | undefined;
2025-10-26 22:01:42 -05:00
deltaType?: CompareCellDirection;
2024-07-12 20:38:19 -05:00
decimals?: number;
deltaNominalDisplay?: (delta: number) => string | number;
indent?: boolean;
stats: TankCharacteristics[];
2025-01-16 20:14:34 -06:00
} & (
| {
name: string;
value: (member: TankCharacteristics) => number | undefined;
}
| {
value: keyof TankCharacteristics;
}
);
2024-07-12 20:38:19 -05:00
export function CompareRow({
display,
2025-10-26 22:01:42 -05:00
deltaType = CompareCellDirection.HIGHER_IS_BETTER,
2024-07-12 20:38:19 -05:00
decimals,
deltaNominalDisplay,
indent,
stats,
2025-01-16 20:14:34 -06:00
...props
2024-07-12 20:38:19 -05:00
}: CompareRowProps) {
2025-01-16 20:14:34 -06:00
const { strings } = useLocale();
2024-07-12 20:38:19 -05:00
const sorting = CompareEphemeral.use((state) => state.sorting);
const deltaMode = ComparePersistent.use((state) => state.deltaMode);
const values = stats.map((stat) =>
2025-09-01 23:05:22 -05:00
typeof props.value === "function"
2025-01-16 20:14:34 -06:00
? props.value(stat)!
2025-09-01 23:05:22 -05:00
: (stat[props.value] as number)
2024-07-12 20:38:19 -05:00
);
2025-01-16 20:14:34 -06:00
const id = useRef(Math.random());
const name =
2025-09-01 23:05:22 -05:00
typeof props.value === "string"
2025-01-16 20:14:34 -06:00
? strings.website.tools.tankopedia.characteristics.values[props.value]
: props.name;
2024-07-12 20:38:19 -05:00
return (
<Table.Row>
<StickyRowHeaderCell>
<Flex
align="center"
2025-09-01 23:05:22 -05:00
style={{ whiteSpace: "nowrap" }}
gap={indent ? { initial: "4", sm: "6" } : { initial: "1", sm: "2" }}
2024-07-12 20:38:19 -05:00
>
<IconButton
2025-09-01 23:05:22 -05:00
color={sorting?.by === id.current ? undefined : "gray"}
2024-07-12 20:38:19 -05:00
variant="ghost"
onClick={() => {
2025-09-01 23:05:22 -05:00
CompareEphemeral.mutate((draft) => {
2024-07-12 20:38:19 -05:00
draft.members.sort((memberA, memberB) => {
const indexA = draft.members.indexOf(memberA);
const indexB = draft.members.indexOf(memberB);
const valueA = values[indexA];
const valueB = values[indexB];
2025-09-01 23:05:22 -05:00
return draft.sorting?.direction === "ascending" &&
2025-01-16 20:14:34 -06:00
draft.sorting.by === id.current
2024-07-12 20:38:19 -05:00
? valueA - valueB
: valueB - valueA;
});
draft.sorting = {
2025-01-16 20:14:34 -06:00
by: id.current,
2024-07-12 20:38:19 -05:00
direction:
2025-09-01 23:05:22 -05:00
draft.sorting?.direction === "ascending" &&
2025-01-16 20:14:34 -06:00
draft.sorting.by === id.current
2025-09-01 23:05:22 -05:00
? "descending"
: "ascending",
2024-07-12 20:38:19 -05:00
};
});
}}
>
2025-01-16 20:14:34 -06:00
{(sorting === undefined || sorting.by !== id.current) && (
2025-09-01 23:05:22 -05:00
<CaretSortIcon style={{ transform: "rotate(90deg)" }} />
2024-07-12 20:38:19 -05:00
)}
2025-01-16 20:14:34 -06:00
{sorting?.by === id.current && (
2024-07-12 20:38:19 -05:00
<>
2025-09-01 23:05:22 -05:00
{sorting.direction === "ascending" && <CaretRightIcon />}
{sorting.direction === "descending" && <CaretLeftIcon />}
2024-07-12 20:38:19 -05:00
</>
)}
</IconButton>
2025-09-01 23:05:22 -05:00
<Text size={{ initial: "1", sm: "2" }}>{name}</Text>
2024-07-12 20:38:19 -05:00
</Flex>
</StickyRowHeaderCell>
{values.map((value, index) => {
const delta = value - values[0];
const deltaPercentage = value / values[0] - 1;
const resolvedDisplayValue = display
? display(stats[index])
: decimals === undefined
2025-10-26 22:01:42 -05:00
? value
: value?.toFixed(decimals);
2024-07-12 20:38:19 -05:00
return (
2025-10-26 22:01:42 -05:00
<CompareCell
2024-07-12 20:38:19 -05:00
key={index}
2025-10-26 22:01:42 -05:00
truth={values[0]}
value={value}
direction={deltaType}
2024-07-12 20:38:19 -05:00
>
<Flex
align="center"
justify="center"
gap="2"
style={{
2025-09-01 23:05:22 -05:00
width: "100%",
height: "100%",
2024-07-12 20:38:19 -05:00
}}
>
2025-09-01 23:05:22 -05:00
<Text style={{ textAlign: "center" }} wrap="nowrap">
2024-07-12 20:38:19 -05:00
{resolvedDisplayValue}
</Text>
{delta !== 0 &&
resolvedDisplayValue !== undefined &&
values[0] !== undefined && (
<>
2025-09-01 23:05:22 -05:00
{deltaMode === "absolute" && (
2024-07-12 20:38:19 -05:00
<Text color="gray">
(
2025-09-01 23:05:22 -05:00
{`${delta > 0 ? "+" : ""}${
2024-07-12 20:38:19 -05:00
deltaNominalDisplay
? deltaNominalDisplay(delta)
: decimals === undefined
2025-10-26 22:01:42 -05:00
? delta
: delta.toFixed(decimals)
2024-07-12 20:38:19 -05:00
}`}
)
</Text>
)}
2025-09-01 23:05:22 -05:00
{deltaMode === "percentage" && (
2024-07-12 20:38:19 -05:00
<Text color="gray">
(
2025-09-01 23:05:22 -05:00
{`${deltaPercentage > 0 ? "+" : ""}${Math.round(
deltaPercentage * 100
2024-07-12 20:38:19 -05:00
)}%`}
)
</Text>
)}
</>
)}
</Flex>
2025-10-26 22:01:42 -05:00
</CompareCell>
2024-07-12 20:38:19 -05:00
);
})}
</Table.Row>
);
}