diff --git a/app/(routes)/movieForm/MovieFormClient.tsx b/app/(routes)/movieForm/MovieFormClient.tsx index 5af7ac7..3f74707 100644 --- a/app/(routes)/movieForm/MovieFormClient.tsx +++ b/app/(routes)/movieForm/MovieFormClient.tsx @@ -5,7 +5,7 @@ import MovieFormFields from "@/components/features/MovieFormFields"; import { useMovieContext } from "@/contexts/MovieContext"; import { MovieFormData } from "@/types/movie"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; const INITIAL_FORM_STATE: MovieFormData = { favouriteMovie: "", @@ -20,6 +20,13 @@ const MovieFormClient = () => { const [currentParticipant, setCurrentParticipant] = useState(1); const [error, setError] = useState(null); + const headingRef = useRef(null); + + useEffect(() => { + if (currentParticipant > 1) { + headingRef.current?.focus(); + } + }, [currentParticipant]); const { formData, @@ -58,7 +65,9 @@ const MovieFormClient = () => { return ( <> -

Person #{currentParticipant}

+

+ Person #{currentParticipant} +

{ {currentParticipant === totalParticipants ? "Get Movies" : "Next"} - {error &&

{error}

} + {error && ( +

+ {error} +

+ )} ); diff --git a/app/(routes)/recommendations/RecommendationsClient.tsx b/app/(routes)/recommendations/RecommendationsClient.tsx index 21e3d20..404e148 100644 --- a/app/(routes)/recommendations/RecommendationsClient.tsx +++ b/app/(routes)/recommendations/RecommendationsClient.tsx @@ -147,13 +147,17 @@ export default function RecommendationsClient() { <>
{error && ( -
+

Oops! Something went wrong

-

+

{error.message.includes("All language models exhausted") ? "Our AI movie experts are currently overwhelmed with requests. Please try again in a few minutes!" : `We ran into a streaming issue: ${error.message}`} @@ -172,9 +176,13 @@ export default function RecommendationsClient() { )} {!hasRenderableCurrentMovie && isLoading && !error && ( -

+
Generating recommendations...
-
+
)} @@ -184,7 +192,7 @@ export default function RecommendationsClient() { className="flex flex-col items-center justify-center p-8 bg-base-200 border border-base-300 rounded-2xl text-center mb-8 mx-auto max-w-md mt-10" >

No strong matches this time

-

+

We could not find a confident recommendation for this group yet. Try broadening the movie preferences or adding a bit more time, then have another go.

@@ -196,7 +204,7 @@ export default function RecommendationsClient() {

{currentMovieName} {currentMovieReleaseYear ? `(${currentMovieReleaseYear})` : ""} {isLoading && currentIndex === recommendedMovies.length - 1 && ( - + )}

{isLoadingPoster ? ( @@ -219,11 +227,11 @@ export default function RecommendationsClient() {
{currentMovieSynopsis || ( - Generating synopsis... + Generating synopsis... )}
-
+
Movie {currentIndex + 1} of {recommendedMovies.length}
- ))} +}) => { + const id = useId(); + const labelId = `${id}-label`; + const panelId = `${id}-panel`; + const tabsRef = useRef<(HTMLButtonElement | null)[]>([]); + + const activeIndex = options.indexOf(value); + + const handleKeyDown = (e: KeyboardEvent) => { + const count = options.length; + let nextIndex: number | null = null; + + switch (e.key) { + case "ArrowRight": + nextIndex = (activeIndex + 1) % count; + break; + case "ArrowLeft": + nextIndex = (activeIndex - 1 + count) % count; + break; + case "Home": + nextIndex = 0; + break; + case "End": + nextIndex = count - 1; + break; + default: + return; + } + + e.preventDefault(); + onChange(options[nextIndex]); + tabsRef.current[nextIndex]?.focus(); + }; + + return ( +
+ + {label} + +
+ {options.map((option, index) => ( + + ))} +
+
+ {value.charAt(0).toUpperCase() + value.slice(1)} selected +
-
-); + ); +}; export default TabGroup; diff --git a/components/ui/TextAreaField.tsx b/components/ui/TextAreaField.tsx index 527ca2f..3961882 100644 --- a/components/ui/TextAreaField.tsx +++ b/components/ui/TextAreaField.tsx @@ -1,3 +1,5 @@ +import { useId } from "react"; + type TextAreaFieldProps = { label: string; name: string; @@ -15,12 +17,17 @@ const TextAreaField = ({ error, placeholder, }: TextAreaFieldProps) => { + const id = useId(); + const textareaId = `${id}-textarea`; + const errorId = `${id}-error`; + return ( -