-
Notifications
You must be signed in to change notification settings - Fork 0
Fix deployed menu and search interactions #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |||||||||||||||||
| * @license | ||||||||||||||||||
| * SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||
| */ | ||||||||||||||||||
| import React, { useState, useEffect, useRef } from 'react'; | ||||||||||||||||||
| import React, { useState, useEffect, useMemo, useRef } from 'react'; | ||||||||||||||||||
| import { Play, Pause, SkipForward, SkipBack, Menu, Search, BookOpen, Disc, Bookmark, Star, Headphones, Rocket, ArrowLeft, Grid, Download, AlertCircle, LoaderCircle } from 'lucide-react'; | ||||||||||||||||||
|
|
||||||||||||||||||
| // --- Types & Mock Data --- | ||||||||||||||||||
|
|
@@ -85,6 +85,9 @@ const MOCK_BOOKS: Book[] = [ | |||||||||||||||||
| export default function App() { | ||||||||||||||||||
| const [currentView, setCurrentView] = useState<ViewState>('library'); | ||||||||||||||||||
| const [selectedBook, setSelectedBook] = useState<Book | null>(null); | ||||||||||||||||||
| const [isMenuOpen, setIsMenuOpen] = useState(false); | ||||||||||||||||||
| const [isSearchOpen, setIsSearchOpen] = useState(false); | ||||||||||||||||||
| const [searchQuery, setSearchQuery] = useState(''); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Audio State | ||||||||||||||||||
| const audioRef = useRef<HTMLAudioElement>(null); | ||||||||||||||||||
|
|
@@ -98,6 +101,16 @@ export default function App() { | |||||||||||||||||
| const [isStandalone, setIsStandalone] = useState(false); | ||||||||||||||||||
| const [isIos, setIsIos] = useState(false); | ||||||||||||||||||
|
|
||||||||||||||||||
| const filteredBooks = useMemo(() => { | ||||||||||||||||||
| const normalizedQuery = searchQuery.trim().toLowerCase(); | ||||||||||||||||||
| if (!normalizedQuery) return MOCK_BOOKS; | ||||||||||||||||||
| return MOCK_BOOKS.filter((book) => { | ||||||||||||||||||
| const bookFields = [book.title, book.author, book.year, book.tag, book.description].join(' ').toLowerCase(); | ||||||||||||||||||
| const trackFields = book.tracks.map((track) => track.title).join(' ').toLowerCase(); | ||||||||||||||||||
| return bookFields.includes(normalizedQuery) || trackFields.includes(normalizedQuery); | ||||||||||||||||||
| }); | ||||||||||||||||||
| }, [searchQuery]); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Fetch real data from IA (Optional enhancement, using mock for stability as requested) | ||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||
| // In a full implementation, we'd fetch from https://archive.org/metadata/IsaacAsimovAudioBookCollection | ||||||||||||||||||
|
|
@@ -169,6 +182,16 @@ export default function App() { | |||||||||||||||||
| }; | ||||||||||||||||||
| }, []); | ||||||||||||||||||
|
|
||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||
| const handleEscape = (event: KeyboardEvent) => { | ||||||||||||||||||
| if (event.key !== 'Escape') return; | ||||||||||||||||||
| setIsMenuOpen(false); | ||||||||||||||||||
| setIsSearchOpen(false); | ||||||||||||||||||
| }; | ||||||||||||||||||
| window.addEventListener('keydown', handleEscape); | ||||||||||||||||||
| return () => window.removeEventListener('keydown', handleEscape); | ||||||||||||||||||
| }, []); | ||||||||||||||||||
|
|
||||||||||||||||||
| const handlePlayPause = async () => { | ||||||||||||||||||
| if (!audioRef.current || !currentTrack) return; | ||||||||||||||||||
| if (isPlaying) { | ||||||||||||||||||
|
|
@@ -231,6 +254,11 @@ export default function App() { | |||||||||||||||||
| setDeferredInstallPrompt(null); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const navigateTo = (view: ViewState) => { | ||||||||||||||||||
| setCurrentView(view); | ||||||||||||||||||
| setIsMenuOpen(false); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const formatTime = (time: number) => { | ||||||||||||||||||
| if (isNaN(time)) return '00:00'; | ||||||||||||||||||
| const m = Math.floor(time / 60); | ||||||||||||||||||
|
|
@@ -251,7 +279,7 @@ export default function App() { | |||||||||||||||||
| <ArrowLeft size={24} /> | ||||||||||||||||||
| </button> | ||||||||||||||||||
| ) : ( | ||||||||||||||||||
| <button className="text-primary hover:text-white transition-colors"> | ||||||||||||||||||
| <button onClick={() => setIsMenuOpen((open) => !open)} className="text-primary hover:text-white transition-colors" aria-label="Open navigation menu"> | ||||||||||||||||||
|
||||||||||||||||||
| <button onClick={() => setIsMenuOpen((open) => !open)} className="text-primary hover:text-white transition-colors" aria-label="Open navigation menu"> | |
| <button | |
| onClick={() => setIsMenuOpen((open) => !open)} | |
| className="text-primary hover:text-white transition-colors" | |
| aria-label={isMenuOpen ? 'Close navigation menu' : 'Open navigation menu'} | |
| aria-expanded={isMenuOpen} | |
| > |
Copilot
AI
Apr 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The search input relies on placeholder text only; it doesn’t have an accessible label. Add an explicit label (visible or screen-reader-only) or an aria-label/aria-labelledby so screen readers can announce what the field is for.
| onChange={(event) => setSearchQuery(event.target.value)} | |
| onChange={(event) => setSearchQuery(event.target.value)} | |
| aria-label="Search titles, authors, tags, and descriptions" |
Copilot
AI
Apr 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backdrop for the open menu uses z-40 while the header/bottom nav use z-50, so users can still interact with those controls while the menu is open (e.g., navigate via bottom nav and leave the menu open). Consider increasing the backdrop z-index above the header/nav (and raising the menu panel above the backdrop), or otherwise ensuring navigation actions also close the menu.
| className="fixed inset-0 z-40 cursor-pointer bg-black/40" | |
| aria-label="Close navigation menu" | |
| /> | |
| <div className="fixed left-4 top-20 z-50 w-60 rounded-xl border border-outline-variant/20 bg-slate-950/95 p-3 shadow-2xl backdrop-blur-xl"> | |
| className="fixed inset-0 z-60 cursor-pointer bg-black/40" | |
| aria-label="Close navigation menu" | |
| /> | |
| <div className="fixed left-4 top-20 z-70 w-60 rounded-xl border border-outline-variant/20 bg-slate-950/95 p-3 shadow-2xl backdrop-blur-xl"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Escape handler closes the search UI (setIsSearchOpen(false)) but leaves searchQuery intact, so the library/collection stay filtered while the query is hidden. Consider clearing searchQuery when closing search (Escape and/or toggle-off), or keeping the search bar visible/indicating an active filter when a query is present to avoid confusing “missing items” states.