feat: convert sport filter to multi-select with mobile bottom sheet (#51)
Replaces the single-sport native <select> with a popover (desktop) and bottom sheet (mobile) checkbox multi-select. No sports selected shows all participants; selecting one or more narrows the list. Trigger label adapts to reflect current filter state and a Reset button appears when filters are active. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5f23885097
commit
da0e0f2be8
3 changed files with 388 additions and 53 deletions
206
app/components/__tests__/AvailableParticipantsSection.test.tsx
Normal file
206
app/components/__tests__/AvailableParticipantsSection.test.tsx
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { AvailableParticipantsSection } from "~/components/draft/AvailableParticipantsSection";
|
||||
|
||||
const defaultProps = {
|
||||
participants: [
|
||||
{ id: "1", name: "Player A", sport: { id: "s1", name: "NFL" } },
|
||||
{ id: "2", name: "Player B", sport: { id: "s2", name: "NBA" } },
|
||||
{ id: "3", name: "Player C", sport: { id: "s1", name: "NFL" } },
|
||||
],
|
||||
searchQuery: "",
|
||||
sportFilters: [] as string[],
|
||||
hideDrafted: false,
|
||||
hideIneligible: false,
|
||||
hideCompletedSports: false,
|
||||
uniqueSports: ["NBA", "NFL"],
|
||||
draftedParticipantIds: new Set<string>(),
|
||||
queue: [] as Array<{ id: string; participantId: string }>,
|
||||
eligibility: null,
|
||||
canPick: false,
|
||||
hasTeam: false,
|
||||
onSearchChange: vi.fn(),
|
||||
onSportFiltersChange: vi.fn(),
|
||||
onHideDraftedChange: vi.fn(),
|
||||
onHideIneligibleChange: vi.fn(),
|
||||
onHideCompletedSportsChange: vi.fn(),
|
||||
onMakePick: vi.fn(),
|
||||
onAddToQueue: vi.fn(),
|
||||
onRemoveFromQueue: vi.fn(),
|
||||
};
|
||||
|
||||
// Both the mobile Sheet trigger and desktop Popover trigger are rendered in jsdom
|
||||
// (CSS breakpoints are not applied). Click the first one to open the Sheet.
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
function clickFirstSportFilterTrigger(name: string | RegExp) {
|
||||
return user.click(screen.getAllByRole("button", { name })[0]);
|
||||
}
|
||||
|
||||
describe("AvailableParticipantsSection", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
describe("Rendering", () => {
|
||||
it("renders all participants by name", () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
expect(screen.getAllByText("Player A").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("Player B").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("Player C").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("renders search input", () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
expect(
|
||||
screen.getByPlaceholderText("Search participants...")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the Show Drafted toggle", () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
expect(screen.getByText("Show Drafted")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders Show Ineligible and Show drafted sports toggles when hasTeam=true", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
hasTeam={true}
|
||||
eligibility={{ eligibleSportIds: new Set(["s1"]), ineligibleReasons: {} }}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText("Show Ineligible")).toBeInTheDocument();
|
||||
expect(screen.getByText("Show drafted sports")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render Show Ineligible when hasTeam=false", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
hasTeam={false}
|
||||
eligibility={{ eligibleSportIds: new Set(), ineligibleReasons: {} }}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByText("Show Ineligible")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders empty state when no participants", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection {...defaultProps} participants={[]} />
|
||||
);
|
||||
expect(screen.getAllByText("No participants found.").length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sport Filter - trigger label", () => {
|
||||
it('shows "All Sports" in aria-label when no filters selected', () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
// Both mobile and desktop triggers share the same label
|
||||
const triggers = screen.getAllByRole("button", {
|
||||
name: "Filter by sport: All Sports",
|
||||
});
|
||||
expect(triggers.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("shows sport name in aria-label when exactly one filter selected", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection {...defaultProps} sportFilters={["NFL"]} />
|
||||
);
|
||||
const triggers = screen.getAllByRole("button", {
|
||||
name: "Filter by sport: NFL",
|
||||
});
|
||||
expect(triggers.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("shows count in aria-label when multiple filters selected", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
sportFilters={["NFL", "NBA"]}
|
||||
/>
|
||||
);
|
||||
const triggers = screen.getAllByRole("button", {
|
||||
name: "Filter by sport: 2 sports selected",
|
||||
});
|
||||
expect(triggers.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("does not render active-filter badges", () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
sportFilters={["NFL", "NBA"]}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByLabelText(/Remove .* filter/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sport Filter - sheet/popover", () => {
|
||||
it("opens and shows checkboxes for each sport", async () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: All Sports");
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getByText("NBA")).toBeInTheDocument();
|
||||
expect(within(dialog).getByText("NFL")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onSportFiltersChange with sport added when checkbox clicked", async () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: All Sports");
|
||||
const dialog = screen.getByRole("dialog");
|
||||
await user.click(within(dialog).getByLabelText("NFL"));
|
||||
expect(defaultProps.onSportFiltersChange).toHaveBeenCalledWith(["NFL"]);
|
||||
});
|
||||
|
||||
it("calls onSportFiltersChange with sport removed when unchecked", async () => {
|
||||
render(
|
||||
<AvailableParticipantsSection
|
||||
{...defaultProps}
|
||||
sportFilters={["NFL", "NBA"]}
|
||||
/>
|
||||
);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: 2 sports selected");
|
||||
const dialog = screen.getByRole("dialog");
|
||||
await user.click(within(dialog).getByLabelText("NFL"));
|
||||
expect(defaultProps.onSportFiltersChange).toHaveBeenCalledWith(["NBA"]);
|
||||
});
|
||||
|
||||
it('does not show "Reset" when no filters active', async () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: All Sports");
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).queryByText("Reset")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Reset" button when filters exist', async () => {
|
||||
render(
|
||||
<AvailableParticipantsSection {...defaultProps} sportFilters={["NFL"]} />
|
||||
);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: NFL");
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getByText("Reset")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onSportFiltersChange([]) when "Reset" clicked', async () => {
|
||||
render(
|
||||
<AvailableParticipantsSection {...defaultProps} sportFilters={["NFL"]} />
|
||||
);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: NFL");
|
||||
const dialog = screen.getByRole("dialog");
|
||||
await user.click(within(dialog).getByText("Reset"));
|
||||
expect(defaultProps.onSportFiltersChange).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
it("Done button closes the sheet", async () => {
|
||||
render(<AvailableParticipantsSection {...defaultProps} />);
|
||||
await clickFirstSportFilterTrigger("Filter by sport: All Sports");
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Done" }));
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,21 @@
|
|||
import { useMemo } from "react";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { ListPlus, ListX } from "lucide-react";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "~/components/ui/popover";
|
||||
import {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetTitle,
|
||||
SheetFooter,
|
||||
SheetClose,
|
||||
} from "~/components/ui/sheet";
|
||||
import { ListPlus, ListX, ChevronDown } from "lucide-react";
|
||||
|
||||
function getParticipantState(
|
||||
participant: { id: string; sport: { id: string } },
|
||||
|
|
@ -31,7 +45,7 @@ interface AvailableParticipantsSectionProps {
|
|||
};
|
||||
}>;
|
||||
searchQuery: string;
|
||||
sportFilter: string;
|
||||
sportFilters: string[];
|
||||
hideDrafted: boolean;
|
||||
hideIneligible: boolean;
|
||||
hideCompletedSports: boolean;
|
||||
|
|
@ -45,7 +59,7 @@ interface AvailableParticipantsSectionProps {
|
|||
canPick: boolean;
|
||||
hasTeam: boolean;
|
||||
onSearchChange: (query: string) => void;
|
||||
onSportFilterChange: (sport: string) => void;
|
||||
onSportFiltersChange: (sports: string[]) => void;
|
||||
onHideDraftedChange: (hide: boolean) => void;
|
||||
onHideIneligibleChange: (hide: boolean) => void;
|
||||
onHideCompletedSportsChange: (hide: boolean) => void;
|
||||
|
|
@ -57,7 +71,7 @@ interface AvailableParticipantsSectionProps {
|
|||
export function AvailableParticipantsSection({
|
||||
participants,
|
||||
searchQuery,
|
||||
sportFilter,
|
||||
sportFilters,
|
||||
hideDrafted,
|
||||
hideIneligible,
|
||||
hideCompletedSports,
|
||||
|
|
@ -68,7 +82,7 @@ export function AvailableParticipantsSection({
|
|||
canPick,
|
||||
hasTeam,
|
||||
onSearchChange,
|
||||
onSportFilterChange,
|
||||
onSportFiltersChange,
|
||||
onHideDraftedChange,
|
||||
onHideIneligibleChange,
|
||||
onHideCompletedSportsChange,
|
||||
|
|
@ -81,15 +95,40 @@ export function AvailableParticipantsSection({
|
|||
[queue]
|
||||
);
|
||||
|
||||
const sportFilterSet = useMemo(() => new Set(sportFilters), [sportFilters]);
|
||||
|
||||
const triggerText =
|
||||
sportFilters.length === 0
|
||||
? "All Sports"
|
||||
: sportFilters.length === 1
|
||||
? sportFilters[0]
|
||||
: `${sportFilters.length} sports`;
|
||||
|
||||
const triggerAriaLabel =
|
||||
sportFilters.length === 0
|
||||
? "Filter by sport: All Sports"
|
||||
: sportFilters.length === 1
|
||||
? `Filter by sport: ${sportFilters[0]}`
|
||||
: `Filter by sport: ${sportFilters.length} sports selected`;
|
||||
|
||||
function handleToggleSport(sport: string, checked: boolean | "indeterminate") {
|
||||
if (checked === true) {
|
||||
onSportFiltersChange([...sportFilters, sport]);
|
||||
} else {
|
||||
onSportFiltersChange(sportFilters.filter((s) => s !== sport));
|
||||
}
|
||||
}
|
||||
|
||||
const emptyMessage = useMemo(() => {
|
||||
if (participants.length > 0) return null;
|
||||
const active: string[] = [];
|
||||
if (hideDrafted) active.push("drafted players");
|
||||
if (hideIneligible && eligibility) active.push("ineligible players");
|
||||
if (hideCompletedSports) active.push("drafted sports");
|
||||
if (sportFilters.length > 0) active.push("other sports");
|
||||
if (active.length === 0) return "No participants found.";
|
||||
return `No participants found. Try showing ${active.join(", ")}.`;
|
||||
}, [participants.length, hideDrafted, hideIneligible, hideCompletedSports, eligibility]);
|
||||
}, [participants.length, hideDrafted, hideIneligible, hideCompletedSports, eligibility, sportFilters]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
|
|
@ -105,19 +144,109 @@ export function AvailableParticipantsSection({
|
|||
/>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* Sport Filter Dropdown */}
|
||||
<select
|
||||
value={sportFilter}
|
||||
onChange={(e) => onSportFilterChange(e.target.value)}
|
||||
className="flex-1 min-w-[120px] px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground"
|
||||
>
|
||||
<option value="all">All Sports</option>
|
||||
{uniqueSports.map((sport) => (
|
||||
<option key={sport} value={sport}>
|
||||
{sport}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Sport Filter Multi-Select */}
|
||||
<div className="flex-1 min-w-[120px]">
|
||||
{/* Mobile: bottom sheet */}
|
||||
<div className="md:hidden">
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
aria-label={triggerAriaLabel}
|
||||
className="w-full justify-between text-base font-normal"
|
||||
>
|
||||
<span className="truncate">{triggerText}</span>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="bottom" className="max-h-[70vh] pt-12" aria-describedby={undefined}>
|
||||
<SheetTitle className="sr-only">Filter by sport</SheetTitle>
|
||||
<div className="overflow-y-auto flex-1 px-4">
|
||||
{uniqueSports.map((sport) => {
|
||||
const checkboxId = `sheet-sport-filter-${sport.replace(/\s+/g, "-").toLowerCase()}`;
|
||||
return (
|
||||
<label
|
||||
key={sport}
|
||||
htmlFor={checkboxId}
|
||||
className="flex items-center gap-4 py-4 border-b last:border-b-0 cursor-pointer text-base"
|
||||
>
|
||||
<Checkbox
|
||||
id={checkboxId}
|
||||
checked={sportFilterSet.has(sport)}
|
||||
onCheckedChange={(checked) => handleToggleSport(sport, checked)}
|
||||
/>
|
||||
{sport}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<SheetFooter className="px-4 pb-8 flex-row gap-2">
|
||||
{sportFilters.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
onClick={() => onSportFiltersChange([])}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
)}
|
||||
<SheetClose asChild>
|
||||
<Button className="flex-1">Done</Button>
|
||||
</SheetClose>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
|
||||
{/* Desktop: popover */}
|
||||
<div className="hidden md:block">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
aria-label={triggerAriaLabel}
|
||||
className="w-full justify-between text-sm font-normal"
|
||||
>
|
||||
<span className="truncate">{triggerText}</span>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-72 p-0" align="start">
|
||||
{sportFilters.length > 0 && (
|
||||
<div className="flex justify-end px-2 pt-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-auto px-2 py-1 text-xs"
|
||||
onClick={() => onSportFiltersChange([])}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="max-h-64 overflow-y-auto p-2 space-y-0.5">
|
||||
{uniqueSports.map((sport) => {
|
||||
const checkboxId = `popover-sport-filter-${sport.replace(/\s+/g, "-").toLowerCase()}`;
|
||||
return (
|
||||
<label
|
||||
key={sport}
|
||||
htmlFor={checkboxId}
|
||||
className="flex items-center gap-2 px-2 py-2 rounded-sm hover:bg-accent cursor-pointer text-sm"
|
||||
>
|
||||
<Checkbox
|
||||
id={checkboxId}
|
||||
checked={sportFilterSet.has(sport)}
|
||||
onCheckedChange={(checked) => handleToggleSport(sport, checked)}
|
||||
/>
|
||||
{sport}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show/Hide Drafted Toggle */}
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md">
|
||||
|
|
@ -156,6 +285,7 @@ export function AvailableParticipantsSection({
|
|||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -434,7 +434,7 @@ export default function DraftRoom() {
|
|||
const [hideDrafted, setHideDrafted] = useState(true);
|
||||
const [hideIneligible, setHideIneligible] = useState(true);
|
||||
const [hideCompletedSports, setHideCompletedSports] = useState(false);
|
||||
const [sportFilter, setSportFilter] = useState<string>("all");
|
||||
const [sportFilters, setSportFilters] = useState<string[]>([]);
|
||||
const [queue, setQueue] = useState<QueueItem[]>(userQueue);
|
||||
const [isPaused, setIsPaused] = useState(season.draftPaused || false);
|
||||
const [isDraftComplete, setIsDraftComplete] = useState(
|
||||
|
|
@ -1275,45 +1275,44 @@ export default function DraftRoom() {
|
|||
);
|
||||
|
||||
// Filter participants based on search, sport, drafted status, and eligibility
|
||||
const filteredParticipants = useMemo(
|
||||
() =>
|
||||
availableParticipants.filter((participant: any) => {
|
||||
// Drafted filter - hide drafted participants by default
|
||||
if (hideDrafted && draftedParticipantIds.has(participant.id)) {
|
||||
const filteredParticipants = useMemo(() => {
|
||||
const sportFilterSet = new Set(sportFilters);
|
||||
return availableParticipants.filter((participant: any) => {
|
||||
// Drafted filter - hide drafted participants by default
|
||||
if (hideDrafted && draftedParticipantIds.has(participant.id)) {
|
||||
return false;
|
||||
}
|
||||
// Eligibility filter - hide ineligible participants by default (if user has a team)
|
||||
if (hideIneligible && eligibility) {
|
||||
const isEligible = eligibility.eligibleSportIds.has(participant.sport.id);
|
||||
if (!isEligible) {
|
||||
return false;
|
||||
}
|
||||
// Eligibility filter - hide ineligible participants by default (if user has a team)
|
||||
if (hideIneligible && eligibility) {
|
||||
const isEligible = eligibility.eligibleSportIds.has(participant.sport.id);
|
||||
if (!isEligible) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Hide completed sports filter - hide participants from sports already drafted by user's team
|
||||
if (hideCompletedSports && userDraftedSportIds.has(participant.sport.id)) {
|
||||
return false;
|
||||
}
|
||||
// Search filter
|
||||
if (
|
||||
searchQuery &&
|
||||
!participant.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// Sport filter
|
||||
if (sportFilter !== "all" && participant.sport.name !== sportFilter) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
[availableParticipants, hideDrafted, hideIneligible, hideCompletedSports, eligibility, draftedParticipantIds, userDraftedSportIds, searchQuery, sportFilter]
|
||||
);
|
||||
}
|
||||
// Hide completed sports filter - hide participants from sports already drafted by user's team
|
||||
if (hideCompletedSports && userDraftedSportIds.has(participant.sport.id)) {
|
||||
return false;
|
||||
}
|
||||
// Search filter
|
||||
if (
|
||||
searchQuery &&
|
||||
!participant.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// Sport filter
|
||||
if (sportFilterSet.size > 0 && !sportFilterSet.has(participant.sport.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [availableParticipants, hideDrafted, hideIneligible, hideCompletedSports, eligibility, draftedParticipantIds, userDraftedSportIds, searchQuery, sportFilters]);
|
||||
|
||||
// Shared component props — defined once to avoid duplication between desktop and mobile layouts
|
||||
const availableParticipantsSectionProps = {
|
||||
participants: filteredParticipants,
|
||||
searchQuery,
|
||||
sportFilter,
|
||||
sportFilters,
|
||||
hideDrafted,
|
||||
hideIneligible,
|
||||
uniqueSports,
|
||||
|
|
@ -1323,7 +1322,7 @@ export default function DraftRoom() {
|
|||
canPick,
|
||||
hasTeam: !!userTeam,
|
||||
onSearchChange: setSearchQuery,
|
||||
onSportFilterChange: setSportFilter,
|
||||
onSportFiltersChange: setSportFilters,
|
||||
onHideDraftedChange: setHideDrafted,
|
||||
onHideIneligibleChange: setHideIneligible,
|
||||
hideCompletedSports,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue