Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/modules/maps/listing/map-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ interface MapCardProps {
map: MapControllerGetMapListingsDataItem | MapControllerGetMapByIdResponse;
className?: string;
expandLowest?: boolean;
starRange?: {
min: number;
max: number;
};
showChips?: boolean;
compact?: boolean;
}

export function MapCard({ map, className, expandLowest, showChips = true, compact = false }: MapCardProps) {
export function MapCard({ map, className, expandLowest, starRange, showChips = true, compact = false }: MapCardProps) {
const t = useTranslations();
const linkSearch = usePersistedLeaderboardSearch();
const displayLeaderboards = getDisplayLeaderboards(map.leaderboards);
Expand Down Expand Up @@ -47,7 +51,11 @@ export function MapCard({ map, className, expandLowest, showChips = true, compac
linkSearch={linkSearch}
className={className}
compact={compact}
pills={showChips ? <MapDifficultyChips mapId={map.id} leaderboards={displayLeaderboards} expandLowest={expandLowest} /> : undefined}
pills={
showChips ? (
<MapDifficultyChips mapId={map.id} leaderboards={displayLeaderboards} expandLowest={expandLowest} starRange={starRange} />
) : undefined
}
mobileMetadata={
<>
{map.bsid && (
Expand Down
4 changes: 2 additions & 2 deletions src/modules/maps/listing/map-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ const SORT_OPTIONS: { value: MapControllerGetMapListingsSortBy }[] = [
{ value: 'totalScores' }
];

const DEFAULT_MIN_STARS = 0;
const DEFAULT_MAX_STARS = 16;
export const DEFAULT_MIN_STARS = 0;
export const DEFAULT_MAX_STARS = 16;

// sorts that imply ranked-only results
const RANKED_SORTS = new Set<MapControllerGetMapListingsSortBy>(['highestStars', 'latestRankedAt']);
Expand Down
6 changes: 4 additions & 2 deletions src/modules/maps/shared/map-difficulty-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ interface MapDifficultyChipProps {
mapId: number;
leaderboard: MapCardLeaderboard;
isExpanded: boolean;
isDimmed?: boolean;
onExpandAction: () => void;
}

export function MapDifficultyChip({ mapId, leaderboard, isExpanded, onExpandAction }: MapDifficultyChipProps) {
export function MapDifficultyChip({ mapId, leaderboard, isExpanded, isDimmed, onExpandAction }: MapDifficultyChipProps) {
const [expandedWidth, setExpandedWidth] = useState(0);
const linkSearch = usePersistedLeaderboardSearch();
const [measureElement, setMeasureElement] = useState<HTMLSpanElement | null>(null);
Expand Down Expand Up @@ -78,10 +79,11 @@ export function MapDifficultyChip({ mapId, leaderboard, isExpanded, onExpandActi
}}
className={cn(
'text-badge-foreground absolute inset-0 flex h-full w-full items-center overflow-hidden rounded-md shadow-sm ring-1 ring-black/10',
transitionsEnabled && 'transition-[box-shadow,padding] duration-200 ease-out',
transitionsEnabled && 'transition-[box-shadow,filter,opacity,padding] duration-200 ease-out',
'hover:shadow-md focus-visible:shadow-md focus-visible:outline-hidden',
'justify-center',
isExpanded && 'px-1',
isDimmed && 'opacity-35 saturate-50 hover:opacity-90 focus-visible:opacity-100',
getDifficultyBgClass(leaderboard.difficulty)
)}
>
Expand Down
34 changes: 29 additions & 5 deletions src/modules/maps/shared/map-difficulty-chips.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
'use client';

import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';

import { type MapCardLeaderboard, MapDifficultyChip } from '@/modules/maps/shared/map-difficulty-chip';

export type StarRangeFilter = {
min: number;
max: number;
};

interface MapDifficultyChipsProps {
mapId: number;
leaderboards: MapCardLeaderboard[];
expandLowest?: boolean;
starRange?: StarRangeFilter;
}

export function MapDifficultyChips({ mapId, leaderboards, expandLowest }: MapDifficultyChipsProps) {
const defaultChipId = (expandLowest ? leaderboards.at(0) : leaderboards.at(-1))?.id ?? null;
export function MapDifficultyChips({ mapId, leaderboards, expandLowest, starRange }: MapDifficultyChipsProps) {
const matchingLeaderboards = starRange ? leaderboards.filter((leaderboard) => matchesStarRange(leaderboard, starRange)) : [];
const defaultLeaderboards = starRange ? matchingLeaderboards : leaderboards;
const defaultChipId = (expandLowest ? defaultLeaderboards.at(0) : defaultLeaderboards.at(-1))?.id ?? null;
const [expandedChipId, setExpandedChipId] = useState<number | null>(defaultChipId);
const starRangeKey = starRange ? `${starRange.min}-${starRange.max}` : '';
const previousStarRangeKey = useRef(starRangeKey);

useEffect(() => {
setExpandedChipId((current) => {
if (current != null && leaderboards.some((lb) => lb.id === current)) return current;
if (previousStarRangeKey.current !== starRangeKey) {
previousStarRangeKey.current = starRangeKey;
return defaultChipId;
}

if (current != null) {
const currentLeaderboard = leaderboards.find((leaderboard) => leaderboard.id === current);
if (currentLeaderboard && (!starRange || matchesStarRange(currentLeaderboard, starRange))) return current;
}
return defaultChipId;
});
}, [defaultChipId, leaderboards]);
}, [defaultChipId, leaderboards, starRange, starRangeKey]);

return (
<div className="flex flex-nowrap items-center gap-1">
Expand All @@ -29,9 +47,15 @@ export function MapDifficultyChips({ mapId, leaderboards, expandLowest }: MapDif
mapId={mapId}
leaderboard={lb}
isExpanded={expandedChipId === lb.id}
isDimmed={!!starRange && !matchesStarRange(lb, starRange)}
onExpandAction={() => setExpandedChipId(lb.id)}
/>
))}
</div>
);
}

function matchesStarRange(leaderboard: MapCardLeaderboard, starRange: StarRangeFilter) {
const stars = leaderboard.realm.stars;
return leaderboard.realm.leaderboardStatus === 'RANKED' && stars >= starRange.min && stars <= starRange.max;
}
13 changes: 11 additions & 2 deletions src/routes/maps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';

import { MapCard } from '@/modules/maps/listing/map-card';
import { MapFilters } from '@/modules/maps/listing/map-filters';
import { DEFAULT_MAX_STARS, DEFAULT_MIN_STARS, MapFilters } from '@/modules/maps/listing/map-filters';
import {
MAP_CONTROLLER_GET_MAP_LISTINGS_SORT_BY,
MAP_CONTROLLER_GET_MAP_LISTINGS_SORT_DIRECTION,
Expand Down Expand Up @@ -100,6 +100,15 @@ function MapsRoute() {
const maps = response.data;
const meta = response.metadata;
const expandLowest = searchParams.sortBy === 'highestStars' && (searchParams.sortDirection ?? 'desc') === 'asc';
const minStars = searchParams.minStars ?? DEFAULT_MIN_STARS;
const maxStars = searchParams.maxStars ?? DEFAULT_MAX_STARS;
const starRange =
minStars !== DEFAULT_MIN_STARS || maxStars !== DEFAULT_MAX_STARS
? {
min: minStars,
max: maxStars
}
: undefined;
const bgCandidates = maps.filter((m) => m.coverUrl).map((m) => m.coverUrl);
const getPageHref = (page: number) => buildMapsHref(updateSearchParams(searchParams, { page: page > 1 ? page : undefined }));

Expand All @@ -119,7 +128,7 @@ function MapsRoute() {

<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
{maps.map((map) => (
<MapCard key={map.id} map={map} expandLowest={expandLowest} />
<MapCard key={map.id} map={map} expandLowest={expandLowest} starRange={starRange} />
))}
</div>

Expand Down