Replace JS based tier width label solution with subgrid

Main goal is to try to fix the infinite loop bug users are reporting
on tier list maker but this also nicely simplifies the code
This commit is contained in:
Kalle
2026-04-05 11:39:42 +03:00
parent ff27d6739b
commit aec4984387
5 changed files with 14 additions and 62 deletions

View File

@@ -1,10 +1,12 @@
.container {
display: flex;
gap: var(--s-1);
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
min-height: 68px;
}
.tierLabel {
grid-column: 1;
display: flex;
align-items: center;
justify-content: center;
@@ -42,7 +44,7 @@
}
.targetZone {
flex: 1;
grid-column: 2;
display: flex;
flex-wrap: wrap;
gap: var(--s-1-5);
@@ -75,6 +77,7 @@
}
.arrowControls {
grid-column: 3;
display: grid;
grid-template-rows: 1fr 1fr;
gap: var(--s-1);

View File

@@ -5,7 +5,6 @@ import {
} from "@dnd-kit/sortable";
import clsx from "clsx";
import { ChevronDown, ChevronUp, Trash } from "lucide-react";
import { useLayoutEffect, useRef } from "react";
import { Button } from "react-aria-components";
import { useTranslation } from "react-i18next";
import { SendouButton } from "~/components/elements/Button";
@@ -37,9 +36,6 @@ export function TierRow({ tier }: TierRowProps) {
handleMoveTierDown,
showTierHeaders,
screenshotMode,
tierLabelWidth,
registerTierLabelWidth,
unregisterTierLabelWidth,
} = useTierListState();
const items = getItemsInTier(tier.id);
@@ -47,40 +43,20 @@ export function TierRow({ tier }: TierRowProps) {
const { setNodeRef, isOver } = useDroppable({
id: tier.id,
});
const labelRef = useRef<HTMLButtonElement>(null);
// biome-ignore lint/correctness/useExhaustiveDependencies: tier.name impacts width
useLayoutEffect(() => {
if (labelRef.current) {
// Temporarily remove width constraint to measure natural width
const currentWidth = labelRef.current.style.width;
labelRef.current.style.width = "auto";
const naturalWidth = labelRef.current.scrollWidth;
labelRef.current.style.width = currentWidth;
registerTierLabelWidth(tier.id, naturalWidth);
}
}, [tier.id, tier.name, registerTierLabelWidth]);
const tierIndex = state.tiers.findIndex((t) => t.id === tier.id);
const isFirstTier = tierIndex === 0;
const isLastTier = tierIndex === state.tiers.length - 1;
const handleDelete = () => {
unregisterTierLabelWidth(tier.id);
handleRemoveTier(tier.id);
};
return (
<div className={styles.container}>
{showTierHeaders ? (
<SendouPopover
trigger={
<Button
ref={labelRef}
className={styles.tierLabel}
style={{
backgroundColor: tier.color,
width: tierLabelWidth,
}}
>
<span
@@ -135,7 +111,7 @@ export function TierRow({ tier }: TierRowProps) {
</div>
<div className="stack horizontal justify-end">
<SendouButton
onPress={handleDelete}
onPress={() => handleRemoveTier(tier.id)}
variant="minimal-destructive"
icon={<Trash />}
/>

View File

@@ -1,14 +1,10 @@
import type { ReactNode } from "react";
import { createContext, useContext, useRef, useState } from "react";
import { createContext, useContext, useState } from "react";
import { useTierList } from "../hooks/useTierList";
import { DEFAULT_TIER_LABEL_WIDTH } from "../tier-list-maker-constants";
type TierListContextType = ReturnType<typeof useTierList> & {
screenshotMode: boolean;
setScreenshotMode: (value: boolean) => void;
tierLabelWidth: number;
registerTierLabelWidth: (tierId: string, width: number) => void;
unregisterTierLabelWidth: (tierId: string) => void;
};
const TierListContext = createContext<TierListContextType | null>(null);
@@ -16,28 +12,6 @@ const TierListContext = createContext<TierListContextType | null>(null);
export function TierListProvider({ children }: { children: ReactNode }) {
const state = useTierList();
const [screenshotMode, setScreenshotMode] = useState(false);
const [tierLabelWidth, setTierLabelWidth] = useState(
DEFAULT_TIER_LABEL_WIDTH,
);
const widthsRef = useRef<Map<string, number>>(new Map());
const registerTierLabelWidth = (tierId: string, width: number) => {
widthsRef.current.set(tierId, width);
const maxWidth = Math.max(...widthsRef.current.values());
if (maxWidth !== tierLabelWidth) {
setTierLabelWidth(maxWidth);
}
};
const unregisterTierLabelWidth = (tierId: string) => {
widthsRef.current.delete(tierId);
const values = [...widthsRef.current.values()];
const maxWidth =
values.length > 0 ? Math.max(...values) : DEFAULT_TIER_LABEL_WIDTH;
if (maxWidth !== tierLabelWidth) {
setTierLabelWidth(maxWidth);
}
};
return (
<TierListContext.Provider
@@ -45,9 +19,6 @@ export function TierListProvider({ children }: { children: ReactNode }) {
...state,
screenshotMode,
setScreenshotMode,
tierLabelWidth,
registerTierLabelWidth,
unregisterTierLabelWidth,
}}
>
{children}

View File

@@ -8,9 +8,10 @@
}
.tierList {
display: flex;
flex-direction: column;
gap: var(--s-2);
display: grid;
grid-template-columns: auto 1fr auto;
row-gap: var(--s-2);
column-gap: var(--s-1);
margin-bottom: var(--s-6);
}
@@ -32,6 +33,7 @@
}
.titleInput {
grid-column: 1 / -1;
width: 100%;
padding: 0 var(--s-2) var(--s-2) var(--s-2);
font-size: var(--font-lg);
@@ -53,6 +55,7 @@
}
.authorSection {
grid-column: 1 / -1;
display: flex;
flex-direction: row;
justify-content: center;

View File

@@ -1,6 +1,5 @@
import type { TierListMakerTier } from "./tier-list-maker-schemas";
export const DEFAULT_TIER_LABEL_WIDTH = 68;
export const TIER_NAME_MAX_LENGTH = 50;
export const TIER_NAME_FONT_SIZE_BREAKPOINTS = [