fix: prevent iOS Safari viewport zoom on draft room form inputs (#45)

Add text-base md:text-sm to all native <input> and <select> elements
in the draft room (including the ShadCN SelectTrigger) so iOS Safari
doesn't auto-zoom when focused.

Also extract the two near-duplicate participant selection dialogs into
a shared ParticipantSelectionDialog component (-236 lines), and replace
O(n) queue.find() scans with a memoized Map in AvailableParticipantsSection.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-02-27 23:46:09 -08:00 committed by GitHub
parent 4e447c30ab
commit 2df47658fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 228 additions and 276 deletions

View file

@ -1,3 +1,4 @@
import { useMemo } from "react";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { ListPlus, ListX } from "lucide-react"; import { ListPlus, ListX } from "lucide-react";
@ -5,14 +6,14 @@ import { ListPlus, ListX } from "lucide-react";
function getParticipantState( function getParticipantState(
participant: { id: string; sport: { id: string } }, participant: { id: string; sport: { id: string } },
draftedParticipantIds: Set<string>, draftedParticipantIds: Set<string>,
queue: Array<{ participantId: string }>, queueMap: Map<string, string>,
eligibility: { eligibility: {
eligibleSportIds: Set<string>; eligibleSportIds: Set<string>;
ineligibleReasons: Record<string, string>; ineligibleReasons: Record<string, string>;
} | null } | null
) { ) {
const isDrafted = draftedParticipantIds.has(participant.id); const isDrafted = draftedParticipantIds.has(participant.id);
const isInQueue = queue.some((item) => item.participantId === participant.id); const isInQueue = queueMap.has(participant.id);
const isEligible = eligibility const isEligible = eligibility
? eligibility.eligibleSportIds.has(participant.sport.id) ? eligibility.eligibleSportIds.has(participant.sport.id)
: true; : true;
@ -71,6 +72,11 @@ export function AvailableParticipantsSection({
onAddToQueue, onAddToQueue,
onRemoveFromQueue, onRemoveFromQueue,
}: AvailableParticipantsSectionProps) { }: AvailableParticipantsSectionProps) {
const queueMap = useMemo(
() => new Map(queue.map((item) => [item.participantId, item.id])),
[queue]
);
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="px-4 pt-4 pb-2 flex-shrink-0"> <div className="px-4 pt-4 pb-2 flex-shrink-0">
@ -81,7 +87,7 @@ export function AvailableParticipantsSection({
placeholder="Search participants..." placeholder="Search participants..."
value={searchQuery} value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)} onChange={(e) => onSearchChange(e.target.value)}
className="w-full px-3 py-2 border rounded-md text-sm bg-background text-foreground" className="w-full px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground"
/> />
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
@ -89,7 +95,7 @@ export function AvailableParticipantsSection({
<select <select
value={sportFilter} value={sportFilter}
onChange={(e) => onSportFilterChange(e.target.value)} onChange={(e) => onSportFilterChange(e.target.value)}
className="flex-1 min-w-[120px] px-3 py-2 border rounded-md text-sm bg-background text-foreground" 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> <option value="all">All Sports</option>
{uniqueSports.map((sport) => ( {uniqueSports.map((sport) => (
@ -135,7 +141,7 @@ export function AvailableParticipantsSection({
) : ( ) : (
participants.map((participant) => { participants.map((participant) => {
const { isDrafted, isInQueue, isEligible, ineligibleReason } = const { isDrafted, isInQueue, isEligible, ineligibleReason } =
getParticipantState(participant, draftedParticipantIds, queue, eligibility); getParticipantState(participant, draftedParticipantIds, queueMap, eligibility);
return ( return (
<div <div
@ -198,12 +204,8 @@ export function AvailableParticipantsSection({
size="sm" size="sm"
className="min-h-[44px] min-w-[44px]" className="min-h-[44px] min-w-[44px]"
onClick={() => { onClick={() => {
const queueItem = queue.find( const queueId = queueMap.get(participant.id);
(item) => item.participantId === participant.id if (queueId) onRemoveFromQueue(queueId);
);
if (queueItem) {
onRemoveFromQueue(queueItem.id);
}
}} }}
title="Remove from queue" title="Remove from queue"
> >
@ -260,7 +262,7 @@ export function AvailableParticipantsSection({
) : ( ) : (
participants.map((participant) => { participants.map((participant) => {
const { isDrafted, isInQueue, isEligible, ineligibleReason } = const { isDrafted, isInQueue, isEligible, ineligibleReason } =
getParticipantState(participant, draftedParticipantIds, queue, eligibility); getParticipantState(participant, draftedParticipantIds, queueMap, eligibility);
return ( return (
<tr <tr
@ -332,13 +334,8 @@ export function AvailableParticipantsSection({
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => { onClick={() => {
const queueItem = queue.find( const queueId = queueMap.get(participant.id);
(item) => if (queueId) onRemoveFromQueue(queueId);
item.participantId === participant.id
);
if (queueItem) {
onRemoveFromQueue(queueItem.id);
}
}} }}
title="Remove from queue" title="Remove from queue"
> >

View file

@ -0,0 +1,188 @@
import { useState, useEffect, useMemo } from "react";
import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
interface ParticipantSelectionDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
title: string;
description: string;
participants: Array<{
id: string;
name: string;
sport: { id: string; name: string };
}>;
draftedParticipantIds: Set<string>;
/** Pass the old participant's ID to keep them in the list (replace-pick dialog) */
allowParticipantId?: string;
eligibility: {
eligibleSportIds: Set<string>;
ineligibleReasons: Record<string, string>;
} | null;
/** Marks a row with "Current" badge and "Keep" button label */
currentParticipantId?: string;
uniqueSports: string[];
onSelect: (participantId: string) => void;
}
export function ParticipantSelectionDialog({
open,
onOpenChange,
title,
description,
participants,
draftedParticipantIds,
allowParticipantId,
eligibility,
currentParticipantId,
uniqueSports,
onSelect,
}: ParticipantSelectionDialogProps) {
const [searchQuery, setSearchQuery] = useState("");
const [sportFilter, setSportFilter] = useState("all");
// Reset filters each time the dialog opens
useEffect(() => {
if (open) {
setSearchQuery("");
setSportFilter("all");
}
}, [open]);
const filtered = useMemo(
() =>
participants.filter((p) => {
if (p.id !== allowParticipantId && draftedParticipantIds.has(p.id))
return false;
if (
searchQuery &&
!p.name.toLowerCase().includes(searchQuery.toLowerCase())
)
return false;
if (sportFilter !== "all" && p.sport.name !== sportFilter) return false;
return true;
}),
[participants, draftedParticipantIds, allowParticipantId, searchQuery, sportFilter]
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col gap-0 p-0">
<DialogHeader className="p-6 pb-4">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<div className="px-6 pb-4">
<div className="flex gap-2 mb-4">
<input
type="text"
placeholder="Search participants..."
className="flex-1 px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<select
className="px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground"
value={sportFilter}
onChange={(e) => setSportFilter(e.target.value)}
>
<option value="all">All Sports</option>
{uniqueSports.map((sport) => (
<option key={sport} value={sport}>
{sport}
</option>
))}
</select>
</div>
<div className="max-h-96 overflow-y-auto border rounded-lg">
<table className="w-full text-sm">
<thead className="bg-muted sticky top-0">
<tr>
<th className="text-left p-3 font-semibold">Participant</th>
<th className="text-left p-3 font-semibold">Sport</th>
<th className="text-right p-3 font-semibold">Action</th>
</tr>
</thead>
<tbody>
{filtered.map((participant) => {
const isCurrentPick =
participant.id === currentParticipantId;
const isEligible = eligibility
? eligibility.eligibleSportIds.has(participant.sport.id)
: true;
const ineligibleReason =
eligibility?.ineligibleReasons[participant.sport.id];
return (
<tr
key={participant.id}
className={`border-t ${
isEligible
? "hover:bg-muted/50"
: "bg-destructive/10 opacity-60"
}`}
title={!isEligible ? ineligibleReason : undefined}
>
<td className="p-3">
<div className="flex items-center gap-2">
<span
className={`font-medium ${!isEligible ? "text-muted-foreground" : ""}`}
>
{participant.name}
</span>
{isCurrentPick && (
<Badge variant="outline" className="text-xs">
Current
</Badge>
)}
{!isEligible && (
<Badge
variant="destructive"
className="text-xs"
title={ineligibleReason}
>
Ineligible
</Badge>
)}
</div>
</td>
<td className="p-3 text-muted-foreground">
{participant.sport.name}
</td>
<td className="p-3 text-right">
<Button
size="sm"
onClick={() => onSelect(participant.id)}
disabled={!isEligible}
title={!isEligible ? ineligibleReason : undefined}
>
{isCurrentPick ? "Keep" : "Draft"}
</Button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
<DialogFooter className="px-6 pb-6">
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -35,7 +35,7 @@ function SelectTrigger({
data-slot="select-trigger" data-slot="select-trigger"
data-size={size} data-size={size}
className={cn( className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-base md:text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}

View file

@ -19,6 +19,7 @@ import { AvailableParticipantsSection } from "~/components/draft/AvailablePartic
import { SidebarRecentPicks } from "~/components/draft/SidebarRecentPicks"; import { SidebarRecentPicks } from "~/components/draft/SidebarRecentPicks";
import { DraftGridSection } from "~/components/draft/DraftGridSection"; import { DraftGridSection } from "~/components/draft/DraftGridSection";
import { ConnectionOverlay } from "~/components/draft/ConnectionOverlay"; import { ConnectionOverlay } from "~/components/draft/ConnectionOverlay";
import { ParticipantSelectionDialog } from "~/components/draft/ParticipantSelectionDialog";
import { calculateDraftEligibility } from "~/lib/draft-eligibility"; import { calculateDraftEligibility } from "~/lib/draft-eligibility";
import { getTeamForPick } from "~/lib/draft-order"; import { getTeamForPick } from "~/lib/draft-order";
import { useDraftNotifications } from "~/hooks/useDraftNotifications"; import { useDraftNotifications } from "~/hooks/useDraftNotifications";
@ -374,9 +375,6 @@ export default function DraftRoom() {
pickNumber: number; pickNumber: number;
teamId: string; teamId: string;
} | null>(null); } | null>(null);
const [dialogSearchQuery, setDialogSearchQuery] = useState("");
const [dialogSportFilter, setDialogSportFilter] = useState<string>("all");
// Replace pick dialog state // Replace pick dialog state
const [replacePickDialogOpen, setReplacePickDialogOpen] = useState(false); const [replacePickDialogOpen, setReplacePickDialogOpen] = useState(false);
const [replacePickSlot, setReplacePickSlot] = useState<{ const [replacePickSlot, setReplacePickSlot] = useState<{
@ -897,8 +895,6 @@ export default function DraftRoom() {
return; return;
} }
setReplacePickSlot({ pickNumber, teamId, oldParticipantId: pick.participant.id }); setReplacePickSlot({ pickNumber, teamId, oldParticipantId: pick.participant.id });
setDialogSearchQuery("");
setDialogSportFilter("all");
setReplacePickDialogOpen(true); setReplacePickDialogOpen(true);
}; };
@ -1012,8 +1008,6 @@ export default function DraftRoom() {
const handleForceManualPickOpen = (pickNumber: number, teamId: string) => { const handleForceManualPickOpen = (pickNumber: number, teamId: string) => {
setSelectedPickSlot({ pickNumber, teamId }); setSelectedPickSlot({ pickNumber, teamId });
setDialogSearchQuery("");
setDialogSportFilter("all");
setForcePickDialogOpen(true); setForcePickDialogOpen(true);
}; };
@ -1083,41 +1077,6 @@ export default function DraftRoom() {
[availableParticipants] [availableParticipants]
); );
// Participants filtered for the force pick dialog (always excludes drafted, uses dialog-local filters)
const dialogFilteredParticipants = useMemo(
() =>
availableParticipants.filter((p: any) => {
if (draftedParticipantIds.has(p.id)) return false;
if (
dialogSearchQuery &&
!p.name.toLowerCase().includes(dialogSearchQuery.toLowerCase())
)
return false;
if (dialogSportFilter !== "all" && p.sport.name !== dialogSportFilter)
return false;
return true;
}),
[availableParticipants, draftedParticipantIds, dialogSearchQuery, dialogSportFilter]
);
// Participants for the replace pick dialog — excludes drafted except the old pick (which is freed)
const replaceDialogFilteredParticipants = useMemo(() => {
if (!replacePickSlot) return [];
return availableParticipants.filter((p: any) => {
// Exclude drafted players, but allow the old participant through since they'll be freed
if (p.id !== replacePickSlot.oldParticipantId && draftedParticipantIds.has(p.id))
return false;
if (
dialogSearchQuery &&
!p.name.toLowerCase().includes(dialogSearchQuery.toLowerCase())
)
return false;
if (dialogSportFilter !== "all" && p.sport.name !== dialogSportFilter)
return false;
return true;
});
}, [availableParticipants, replacePickSlot, draftedParticipantIds, dialogSearchQuery, dialogSportFilter]);
// Filter participants based on search, sport, drafted status, and eligibility // Filter participants based on search, sport, drafted status, and eligibility
const filteredParticipants = useMemo( const filteredParticipants = useMemo(
() => () =>
@ -1524,229 +1483,37 @@ export default function DraftRoom() {
})} })}
</nav> </nav>
{/* Force Manual Pick Dialog */} <ParticipantSelectionDialog
<Dialog
open={forcePickDialogOpen && !!selectedPickSlot} open={forcePickDialogOpen && !!selectedPickSlot}
onOpenChange={(open) => { onOpenChange={(open) => {
setForcePickDialogOpen(open); setForcePickDialogOpen(open);
if (!open) setSelectedPickSlot(null); if (!open) setSelectedPickSlot(null);
}} }}
> title="Force Manual Pick"
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col gap-0 p-0"> description={`Select a participant to draft for Pick #${selectedPickSlot?.pickNumber}`}
<DialogHeader className="p-6 pb-4"> participants={availableParticipants}
<DialogTitle>Force Manual Pick</DialogTitle> draftedParticipantIds={draftedParticipantIds}
<DialogDescription> eligibility={forcePickEligibility}
Select a participant to draft for Pick #{selectedPickSlot?.pickNumber} uniqueSports={uniqueSports}
</DialogDescription> onSelect={handleForceManualPick}
</DialogHeader> />
<div className="px-6 pb-4"> <ParticipantSelectionDialog
{/* Search and Filter */}
<div className="flex gap-2 mb-4">
<input
type="text"
placeholder="Search participants..."
className="flex-1 px-3 py-2 border rounded-md bg-background text-foreground"
value={dialogSearchQuery}
onChange={(e) => setDialogSearchQuery(e.target.value)}
/>
<select
className="px-3 py-2 border rounded-md bg-background text-foreground"
value={dialogSportFilter}
onChange={(e) => setDialogSportFilter(e.target.value)}
>
<option value="all">All Sports</option>
{uniqueSports.map((sport) => (
<option key={sport} value={sport}>
{sport}
</option>
))}
</select>
</div>
{/* Participant List */}
<div className="max-h-96 overflow-y-auto border rounded-lg">
<table className="w-full text-sm">
<thead className="bg-muted sticky top-0">
<tr>
<th className="text-left p-3 font-semibold">Participant</th>
<th className="text-left p-3 font-semibold">Sport</th>
<th className="text-right p-3 font-semibold">Action</th>
</tr>
</thead>
<tbody>
{dialogFilteredParticipants.map((participant: any) => {
const isEligible = forcePickEligibility
? forcePickEligibility.eligibleSportIds.has(participant.sport.id)
: true;
const ineligibleReason = forcePickEligibility?.ineligibleReasons[participant.sport.id];
return (
<tr
key={participant.id}
className={`border-t ${
isEligible ? "hover:bg-muted/50" : "bg-destructive/10 opacity-60"
}`}
title={!isEligible ? ineligibleReason : undefined}
>
<td className="p-3">
<div className="flex items-center gap-2">
<span className={`font-medium ${!isEligible ? "text-muted-foreground" : ""}`}>
{participant.name}
</span>
{!isEligible && (
<Badge
variant="destructive"
className="text-xs"
title={ineligibleReason}
>
Ineligible
</Badge>
)}
</div>
</td>
<td className="p-3 text-muted-foreground">
{participant.sport.name}
</td>
<td className="p-3 text-right">
<Button
size="sm"
onClick={() => handleForceManualPick(participant.id)}
disabled={!isEligible}
title={!isEligible ? ineligibleReason : undefined}
>
Draft
</Button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
<DialogFooter className="px-6 pb-6">
<Button variant="outline" onClick={() => setForcePickDialogOpen(false)}>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Replace Pick Dialog */}
<Dialog
open={replacePickDialogOpen && !!replacePickSlot} open={replacePickDialogOpen && !!replacePickSlot}
onOpenChange={(open) => { onOpenChange={(open) => {
setReplacePickDialogOpen(open); setReplacePickDialogOpen(open);
if (!open) setReplacePickSlot(null); if (!open) setReplacePickSlot(null);
}} }}
> title="Replace Pick"
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col gap-0 p-0"> description={`Select a participant to replace the current pick at slot #${replacePickSlot?.pickNumber}`}
<DialogHeader className="p-6 pb-4"> participants={availableParticipants}
<DialogTitle>Replace Pick</DialogTitle> draftedParticipantIds={draftedParticipantIds}
<DialogDescription> allowParticipantId={replacePickSlot?.oldParticipantId}
Select a participant to replace the current pick at slot #{replacePickSlot?.pickNumber} currentParticipantId={replacePickSlot?.oldParticipantId}
</DialogDescription> eligibility={replacePickEligibility}
</DialogHeader> uniqueSports={uniqueSports}
onSelect={handleReplacePick}
<div className="px-6 pb-4"> />
<div className="flex gap-2 mb-4">
<input
type="text"
placeholder="Search participants..."
className="flex-1 px-3 py-2 border rounded-md bg-background text-foreground"
value={dialogSearchQuery}
onChange={(e) => setDialogSearchQuery(e.target.value)}
/>
<select
className="px-3 py-2 border rounded-md bg-background text-foreground"
value={dialogSportFilter}
onChange={(e) => setDialogSportFilter(e.target.value)}
>
<option value="all">All Sports</option>
{uniqueSports.map((sport) => (
<option key={sport} value={sport}>
{sport}
</option>
))}
</select>
</div>
<div className="max-h-96 overflow-y-auto border rounded-lg">
<table className="w-full text-sm">
<thead className="bg-muted sticky top-0">
<tr>
<th className="text-left p-3 font-semibold">Participant</th>
<th className="text-left p-3 font-semibold">Sport</th>
<th className="text-right p-3 font-semibold">Action</th>
</tr>
</thead>
<tbody>
{replaceDialogFilteredParticipants.map((participant: any) => {
const isCurrentPick = participant.id === replacePickSlot?.oldParticipantId;
const isEligible = replacePickEligibility
? replacePickEligibility.eligibleSportIds.has(participant.sport.id)
: true;
const ineligibleReason = replacePickEligibility?.ineligibleReasons[participant.sport.id];
return (
<tr
key={participant.id}
className={`border-t ${
isEligible ? "hover:bg-muted/50" : "bg-destructive/10 opacity-60"
}`}
title={!isEligible ? ineligibleReason : undefined}
>
<td className="p-3">
<div className="flex items-center gap-2">
<span className={`font-medium ${!isEligible ? "text-muted-foreground" : ""}`}>
{participant.name}
</span>
{isCurrentPick && (
<Badge variant="outline" className="text-xs">
Current
</Badge>
)}
{!isEligible && (
<Badge
variant="destructive"
className="text-xs"
title={ineligibleReason}
>
Ineligible
</Badge>
)}
</div>
</td>
<td className="p-3 text-muted-foreground">
{participant.sport.name}
</td>
<td className="p-3 text-right">
<Button
size="sm"
onClick={() => handleReplacePick(participant.id)}
disabled={!isEligible}
title={!isEligible ? ineligibleReason : undefined}
>
{isCurrentPick ? "Keep" : "Draft"}
</Button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
<DialogFooter className="px-6 pb-6">
<Button variant="outline" onClick={() => setReplacePickDialogOpen(false)}>
Cancel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Rollback Confirmation Dialog */} {/* Rollback Confirmation Dialog */}
<Dialog <Dialog
@ -1828,7 +1595,7 @@ export default function DraftRoom() {
step="any" step="any"
value={timeBankAmount} value={timeBankAmount}
onChange={(e) => setTimeBankAmount(e.target.value)} onChange={(e) => setTimeBankAmount(e.target.value)}
className="flex-1 px-3 py-2 border rounded-md bg-background text-foreground text-sm" className="flex-1 px-3 py-2 border rounded-md bg-background text-foreground text-base md:text-sm"
placeholder="Amount" placeholder="Amount"
/> />
<select <select
@ -1836,7 +1603,7 @@ export default function DraftRoom() {
onChange={(e) => onChange={(e) =>
setTimeBankUnit(e.target.value as "seconds" | "minutes" | "hours") setTimeBankUnit(e.target.value as "seconds" | "minutes" | "hours")
} }
className="px-3 py-2 border rounded-md bg-background text-foreground text-sm" className="px-3 py-2 border rounded-md bg-background text-foreground text-base md:text-sm"
> >
<option value="seconds">Seconds</option> <option value="seconds">Seconds</option>
<option value="minutes">Minutes</option> <option value="minutes">Minutes</option>