diff --git a/app/components/AutodraftSettings.tsx b/app/components/AutodraftSettings.tsx index d033216..dbc64c4 100644 --- a/app/components/AutodraftSettings.tsx +++ b/app/components/AutodraftSettings.tsx @@ -200,8 +200,26 @@ function AutodraftOptions({ const isDisabled = isMyTurn; + function handleGroupKeyDown(e: React.KeyboardEvent) { + if (isDisabled) return; + if (!["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft"].includes(e.key)) return; + e.preventDefault(); + const idx = OPTION_ORDER.indexOf(localState); + const next = e.key === "ArrowDown" || e.key === "ArrowRight" + ? (idx + 1) % OPTION_ORDER.length + : (idx - 1 + OPTION_ORDER.length) % OPTION_ORDER.length; + const nextState = OPTION_ORDER[next]; + handleStateChange(nextState); + e.currentTarget.querySelector(`[data-radio-value="${nextState}"]`)?.focus(); + } + return ( -
+
{OPTION_ORDER.map((state) => { const { label } = OPTIONS[state]; const isActive = localState === state; @@ -209,6 +227,10 @@ function AutodraftOptions({ ); @@ -385,6 +407,19 @@ export function AutodraftSettings({ const isDisabled = isMyTurn; + function handleGroupKeyDown(e: React.KeyboardEvent) { + if (isDisabled) return; + if (!["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft"].includes(e.key)) return; + e.preventDefault(); + const idx = OPTION_ORDER.indexOf(localState); + const next = e.key === "ArrowDown" || e.key === "ArrowRight" + ? (idx + 1) % OPTION_ORDER.length + : (idx - 1 + OPTION_ORDER.length) % OPTION_ORDER.length; + const nextState = OPTION_ORDER[next]; + handleStateChange(nextState); + e.currentTarget.querySelector(`[data-radio-value="${nextState}"]`)?.focus(); + } + return (
{/* Header with info icon inline */} @@ -417,7 +452,7 @@ export function AutodraftSettings({
-
+
{OPTION_ORDER.map((state) => { const { label } = OPTIONS[state]; const isActive = localState === state; @@ -425,6 +460,10 @@ export function AutodraftSettings({ ); diff --git a/app/components/NotificationSettings.tsx b/app/components/NotificationSettings.tsx index 51d9225..3bf1d8f 100644 --- a/app/components/NotificationSettings.tsx +++ b/app/components/NotificationSettings.tsx @@ -28,6 +28,7 @@ export function NotificationSettings({ return ( <> 0) { return (
- +
); } else if (change < 0) { return (
- +
); } else { return (
- +
); @@ -101,26 +101,26 @@ export function StandingsTable({ }; return ( - +
- Rank - {showMovement && Change} - Team - Total Points + Rank + {showMovement && Change} + Team + Total Points {showPlacementBreakdown && ( <> - 🥇 - 🥈 - 🥉 - 4th - 5th - 6th - 7th - 8th + + + + 4th + 5th + 6th + 7th + 8th )} - Remaining + Remaining diff --git a/app/components/UserMenu.tsx b/app/components/UserMenu.tsx index 76763d6..7eed617 100644 --- a/app/components/UserMenu.tsx +++ b/app/components/UserMenu.tsx @@ -32,7 +32,7 @@ export function UserMenu({ return ( - diff --git a/app/components/draft/AvailableParticipantsSection.tsx b/app/components/draft/AvailableParticipantsSection.tsx index dbe8d78..f08618c 100644 --- a/app/components/draft/AvailableParticipantsSection.tsx +++ b/app/components/draft/AvailableParticipantsSection.tsx @@ -339,7 +339,9 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
+
-
+ + ) { + if (disabled) return; + if (!["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft"].includes(e.key)) return; + e.preventDefault(); + const idx = MODES.findIndex((m) => m.value === value); + const next = e.key === "ArrowDown" || e.key === "ArrowRight" + ? (idx + 1) % MODES.length + : (idx - 1 + MODES.length) % MODES.length; + const nextValue = MODES[next].value; + onChange(nextValue); + e.currentTarget.querySelector(`[data-radio-value="${nextValue}"]`)?.focus(); + } + return ( -
+
{MODES.map((mode) => { const selected = value === mode.value; return ( +
  • + +
  • ); })} -
    -
    + + -
    + ); } diff --git a/app/components/league/settings/DraftSetupSection.tsx b/app/components/league/settings/DraftSetupSection.tsx index d56577e..e34b16a 100644 --- a/app/components/league/settings/DraftSetupSection.tsx +++ b/app/components/league/settings/DraftSetupSection.tsx @@ -122,11 +122,12 @@ export function DraftSetupSection({
    - +
    ))} diff --git a/app/components/marketing/Footer.tsx b/app/components/marketing/Footer.tsx index a50f889..06f977e 100644 --- a/app/components/marketing/Footer.tsx +++ b/app/components/marketing/Footer.tsx @@ -11,8 +11,7 @@ const FOOTER_LINKS = [ export function Footer() { return (
    © 2026 Brackt. All rights reserved.
    + - - + + @@ -29,10 +30,11 @@ export function QualifyingPointsTable({ className }: TableProps) { return (
    Scoring points by finish position
    FinishPointsFinishPoints
    + - - + + diff --git a/app/hooks/useFocusTrap.ts b/app/hooks/useFocusTrap.ts new file mode 100644 index 0000000..c5d70ae --- /dev/null +++ b/app/hooks/useFocusTrap.ts @@ -0,0 +1,31 @@ +import { useEffect, type RefObject } from "react"; + +const FOCUSABLE_SELECTOR = + 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'; + +export function useFocusTrap(ref: RefObject, active: boolean) { + useEffect(() => { + if (!active) return; + const el = ref.current; + if (!el) return; + + const first = el.querySelector(FOCUSABLE_SELECTOR) ?? el; + first.focus(); + + function onKeyDown(e: KeyboardEvent) { + if (e.key !== "Tab") return; + const focusables = Array.from(el?.querySelectorAll(FOCUSABLE_SELECTOR) ?? []); + if (focusables.length === 0) return; + const firstEl = focusables[0]; + const lastEl = focusables[focusables.length - 1]; + if (e.shiftKey) { + if (document.activeElement === firstEl) { e.preventDefault(); lastEl.focus(); } + } else { + if (document.activeElement === lastEl) { e.preventDefault(); firstEl.focus(); } + } + } + + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, [ref, active]); +} diff --git a/app/root.tsx b/app/root.tsx index d9dc04d..4ddd0d4 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -77,6 +77,12 @@ export function Layout({ children }: { children: React.ReactNode }) { + + Skip to main content + {children} @@ -103,7 +109,7 @@ export default function App({ loaderData }: Route.ComponentProps) { ) : ( <> -
    +
    diff --git a/app/routes/admin.tsx b/app/routes/admin.tsx index 1fb88f5..a7338f8 100644 --- a/app/routes/admin.tsx +++ b/app/routes/admin.tsx @@ -43,7 +43,7 @@ export default function AdminLayout() {

    Admin Panel

    -
    Qualifying points by major finish position
    Major FinishQualifying PointsMajor FinishQualifying Points