Mobile Draft fixes.

This commit is contained in:
Chris Parsons 2026-04-19 21:18:57 -07:00
parent 26cfb04990
commit 272110a934
5 changed files with 229 additions and 111 deletions

View file

@ -117,6 +117,14 @@ html {
animation: pulse-gradient 2s ease-in-out infinite;
}
@keyframes rpf-slide-in {
from { transform: translateY(-32px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.rpf-animate {
animation: rpf-slide-in 0.35s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* NProgress theme override */
#nprogress .bar {
background: var(--electric) !important;

View file

@ -204,7 +204,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
? "All Sports"
: sportFilters.length === 1
? sportFilters[0]
: `${sportFilters.length} sports`,
: `${sportFilters.length} Sports`,
[sportFilters]
);
@ -268,13 +268,14 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
</div>
)}
<div className="px-4 pt-4 pb-2 flex-shrink-0">
<div className="flex flex-wrap gap-2 items-center">
<div className="flex flex-col gap-2 md:flex-row md:flex-wrap md:items-center">
<div className="flex gap-2 md:contents">
<input
type="text"
placeholder="Search participants..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
className="flex-1 min-w-0 px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground"
className="flex-1 min-w-0 px-3 py-2 h-9 border rounded-md text-base md:text-sm bg-background text-foreground"
/>
<div className="md:hidden shrink-0">
@ -355,8 +356,10 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
</PopoverContent>
</Popover>
</div>
</div>
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md shrink-0">
<div className="flex gap-2 flex-wrap md:contents">
<label className="flex flex-1 items-center gap-2 text-sm cursor-pointer px-3 py-2 border rounded-md md:shrink-0 md:flex-none">
<Checkbox
checked={!hideDrafted}
onCheckedChange={(checked) => onHideDraftedChange(checked === false)}
@ -365,7 +368,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
</label>
{hasTeam && eligibility && (
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md shrink-0">
<label className="flex flex-1 items-center gap-2 text-sm cursor-pointer px-3 py-2 border rounded-md md:shrink-0 md:flex-none">
<Checkbox
checked={!hideIneligible}
onCheckedChange={(checked) => onHideIneligibleChange(checked === false)}
@ -375,6 +378,7 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
)}
</div>
</div>
</div>
<div className={`hidden md:grid ${desktopGridClass} bg-muted border-b text-sm font-semibold flex-shrink-0 px-4`}>
<span className="text-center p-3 px-0">OVR</span>

View file

@ -78,6 +78,8 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
const prevRoundRef = useRef(currentRound);
const firstRowRef = useRef<HTMLDivElement>(null);
const [rowHeight, setRowHeight] = useState(0);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const currentCellRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (firstRowRef.current) {
@ -85,6 +87,16 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
}
}, [draftGrid]);
useEffect(() => {
const container = scrollContainerRef.current;
const cell = currentCellRef.current;
if (!container || !cell) return;
const containerRect = container.getBoundingClientRect();
const cellRect = cell.getBoundingClientRect();
const scrollLeft = container.scrollLeft + cellRect.left - containerRect.left - (containerRect.width - cellRect.width) / 2;
container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
}, [currentPick]);
useEffect(() => {
const prev = prevRoundRef.current;
prevRoundRef.current = currentRound;
@ -132,7 +144,7 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
: [displayedIndices[0], displayedIndices[1]];
return (
<div className="overflow-x-auto">
<div ref={scrollContainerRef} className="overflow-x-auto">
<div className="inline-block min-w-full">
{/* Team header */}
<div className="flex gap-1.5 mb-1.5">
@ -191,6 +203,7 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<DraftPickCell
ref={isCurrent ? currentCellRef : undefined}
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
@ -254,6 +267,7 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
return (
<DraftPickCell
key={cell.pickNumber}
ref={isCurrent ? currentCellRef : undefined}
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}

View file

@ -0,0 +1,56 @@
import { memo, useEffect, useRef, useState } from "react";
type Pick = {
id: string;
pickNumber: number;
participant: { name: string };
sport: { name: string };
team: { name: string };
};
export const RecentPicksFeed = memo(function RecentPicksFeed({ picks }: { picks: Pick[] }) {
const recentPicks = picks.slice(-3).reverse();
const prevNewestIdRef = useRef<string | undefined>(picks.at(-1)?.id);
const [animatingId, setAnimatingId] = useState<string | undefined>(undefined);
useEffect(() => {
const newest = recentPicks[0];
if (newest && newest.id !== prevNewestIdRef.current) {
prevNewestIdRef.current = newest.id;
setAnimatingId(newest.id);
const t = setTimeout(() => setAnimatingId(undefined), 450);
return () => clearTimeout(t);
}
}, [recentPicks]);
return (
<div className="flex flex-col gap-1 px-3 pt-2 pb-1 overflow-hidden">
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/60 mb-0.5">Latest Picks</p>
{picks.length === 0 ? (
<p className="text-xs text-muted-foreground py-1">No picks yet</p>
) : (
recentPicks.map((pick, index) => (
<div
key={pick.id}
className={`w-full ${index === 0 && animatingId === pick.id ? "rpf-animate" : ""} bg-muted rounded-lg px-3 py-1.5 flex items-center gap-2`}
>
<span className="text-xs font-bold text-muted-foreground shrink-0 tabular-nums">
#{pick.pickNumber}
</span>
<div className="flex flex-col flex-1 min-w-0">
<span className="text-sm font-medium truncate">
{pick.participant.name}
</span>
<span className="text-xs text-muted-foreground/70 truncate">
{pick.sport.name}
</span>
</div>
<span className="text-xs text-muted-foreground truncate max-w-[90px] text-right shrink-0">
{pick.team.name}
</span>
</div>
))
)}
</div>
);
});

View file

@ -18,6 +18,7 @@ import { DraftSummaryView } from "~/components/draft/DraftSummaryView";
import { QueueSection } from "~/components/draft/QueueSection";
import { AvailableParticipantsSection } from "~/components/draft/AvailableParticipantsSection";
import { SidebarRecentPicks } from "~/components/draft/SidebarRecentPicks";
import { RecentPicksFeed } from "~/components/draft/RecentPicksFeed";
import { DraftGridSection } from "~/components/draft/DraftGridSection";
import { ConnectionOverlay } from "~/components/draft/ConnectionOverlay";
import { AuthRecoveryOverlay } from "~/components/draft/AuthRecoveryOverlay";
@ -46,7 +47,7 @@ type AutodraftStatusEntry = {
};
const MOBILE_TABS_BASE = [
{ id: "available" as const, label: "Available", Icon: Users },
{ id: "available" as const, label: "Participants", Icon: Users },
{ id: "board" as const, label: "Board", Icon: LayoutGrid },
{ id: "teams" as const, label: "Teams", Icon: ListChecks },
{ id: "controls" as const, label: "Controls", Icon: Settings },
@ -544,6 +545,7 @@ export default function DraftRoom() {
!userTeam && isCommissioner ? "board" : "available"
);
const [teamsSubTab, setTeamsSubTab] = useState<"rosters" | "summary">("rosters");
const [boardSubTab, setBoardSubTab] = useState<"board" | "pick-list">("board");
const isMobile = useMediaQuery("(max-width: 767px)");
// Track autodraft status for all teams
@ -1658,7 +1660,7 @@ export default function DraftRoom() {
<img src="/logomark.svg" alt="Brackt" className="h-8" />
</Link>
<h1 className="text-lg md:text-xl font-bold">
{season.league.name} Draft Room
Draft Room
</h1>
</div>
<div className="flex items-center gap-3">
@ -1765,7 +1767,14 @@ export default function DraftRoom() {
{isMobile ? (
<div className="flex h-full overflow-hidden flex-col">
{mobileTab === "available" && (
<AvailableParticipantsSection {...availableParticipantsSectionProps} />
<div className="flex flex-col h-full overflow-hidden">
<div className="flex-shrink-0">
<RecentPicksFeed picks={picks} />
</div>
<div className="flex-1 min-h-0 overflow-hidden">
<AvailableParticipantsSection {...availableParticipantsSectionProps} miniDraftGrid={undefined} />
</div>
</div>
)}
{mobileTab === "queue" && (
<div className="h-full overflow-y-auto">
@ -1779,7 +1788,39 @@ export default function DraftRoom() {
</div>
)}
{mobileTab === "board" && (
<div className="flex flex-col h-full overflow-hidden">
<div className="flex-shrink-0 flex gap-1 p-2 border-b">
<button
onClick={() => setBoardSubTab("board")}
className={`flex-1 py-1.5 text-sm font-medium rounded transition-colors ${
boardSubTab === "board"
? "bg-accent text-accent-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
Board
</button>
<button
onClick={() => setBoardSubTab("pick-list")}
className={`flex-1 py-1.5 text-sm font-medium rounded transition-colors ${
boardSubTab === "pick-list"
? "bg-accent text-accent-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
Pick List
</button>
</div>
<div className="flex-1 overflow-hidden">
{boardSubTab !== "pick-list" ? (
<DraftGridSection {...draftGridSectionProps} />
) : (
<div className="h-full overflow-y-auto p-4">
<SidebarRecentPicks picks={picks} />
</div>
)}
</div>
</div>
)}
{mobileTab === "teams" && (
<div className="flex flex-col h-full overflow-hidden">
@ -1833,11 +1874,6 @@ export default function DraftRoom() {
)
)}
<section>
<h2 className="font-semibold text-sm mb-2">Recent Picks</h2>
<SidebarRecentPicks picks={picks} />
</section>
<Button variant="outline" asChild className="w-full min-h-[48px]">
<Link to={`/leagues/${season.leagueId}`}>Exit Draft Room</Link>
</Button>