diff --git a/frontend/src/pages/FeedbackPage.tsx b/frontend/src/pages/FeedbackPage.tsx index 118399da..f9106315 100644 --- a/frontend/src/pages/FeedbackPage.tsx +++ b/frontend/src/pages/FeedbackPage.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useRef, useEffect } from 'react' import { MessageSquareText, Send, CheckCircle2, ChevronDown } from 'lucide-react' import { useAuthStore } from '@/store/authStore' import { feedbackApi } from '@/api' @@ -26,6 +26,9 @@ export function FeedbackPage() { const [isSubmitting, setIsSubmitting] = useState(false) const [submitted, setSubmitted] = useState(false) const [typeDropdownOpen, setTypeDropdownOpen] = useState(false) + const [highlightedIndex, setHighlightedIndex] = useState(-1) + const triggerRef = useRef(null) + const listboxRef = useRef(null) const canSubmit = email.trim() && feedbackType && message.trim().length >= 10 @@ -34,8 +37,70 @@ export function FeedbackPage() { const handleSelectType = (value: string) => { setFeedbackType(value) setTypeDropdownOpen(false) + setHighlightedIndex(-1) + triggerRef.current?.focus() } + const handleDropdownKeyDown = (e: React.KeyboardEvent) => { + switch (e.key) { + case 'ArrowDown': + e.preventDefault() + if (!typeDropdownOpen) { + setTypeDropdownOpen(true) + setHighlightedIndex(0) + } else { + setHighlightedIndex(i => (i + 1) % FEEDBACK_TYPES.length) + } + break + case 'ArrowUp': + e.preventDefault() + if (!typeDropdownOpen) { + setTypeDropdownOpen(true) + setHighlightedIndex(FEEDBACK_TYPES.length - 1) + } else { + setHighlightedIndex(i => (i - 1 + FEEDBACK_TYPES.length) % FEEDBACK_TYPES.length) + } + break + case 'Enter': + case ' ': + e.preventDefault() + if (typeDropdownOpen && highlightedIndex >= 0) { + handleSelectType(FEEDBACK_TYPES[highlightedIndex].value) + } else { + setTypeDropdownOpen(true) + setHighlightedIndex(0) + } + break + case 'Escape': + e.preventDefault() + setTypeDropdownOpen(false) + setHighlightedIndex(-1) + triggerRef.current?.focus() + break + case 'Tab': + if (typeDropdownOpen) { + setTypeDropdownOpen(false) + setHighlightedIndex(-1) + } + break + } + } + + useEffect(() => { + if (!typeDropdownOpen) return + const handleClickOutside = (e: MouseEvent) => { + if ( + triggerRef.current && !triggerRef.current.contains(e.target as Node) && + listboxRef.current && !listboxRef.current.contains(e.target as Node) + ) { + setTypeDropdownOpen(false) + setHighlightedIndex(-1) + } + } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, [typeDropdownOpen]) + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!canSubmit || isSubmitting) return @@ -119,8 +184,15 @@ export function FeedbackPage() {
{typeDropdownOpen && ( -
- {FEEDBACK_TYPES.map(type => ( +
+ {FEEDBACK_TYPES.map((type, index) => (