feat: implement search-first Bible navigator with touch optimization
Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
95
components/bible/search-navigator.tsx
Normal file
95
components/bible/search-navigator.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Search, Close } from '@mui/icons-material'
|
||||
import { Box, TextField, InputAdornment, Paper, List, ListItem, ListItemButton, Typography } from '@mui/material'
|
||||
import { searchBooks, type SearchResult } from '@/lib/bible-search'
|
||||
|
||||
interface SearchNavigatorProps {
|
||||
onNavigate: (bookId: number, chapter: number) => void
|
||||
}
|
||||
|
||||
export function SearchNavigator({ onNavigate }: SearchNavigatorProps) {
|
||||
const [query, setQuery] = useState('')
|
||||
const [results, setResults] = useState<SearchResult[]>([])
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (query.trim()) {
|
||||
setResults(searchBooks(query))
|
||||
setIsOpen(true)
|
||||
} else {
|
||||
setResults([])
|
||||
setIsOpen(false)
|
||||
}
|
||||
}, [query])
|
||||
|
||||
const handleSelect = (result: SearchResult) => {
|
||||
onNavigate(result.bookId, result.chapter)
|
||||
setQuery('')
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ position: 'relative', width: '100%' }}>
|
||||
<TextField
|
||||
placeholder="Search Bible (e.g., Genesis 1, John 3:16)"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onFocus={() => query && setIsOpen(true)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search sx={{ color: 'text.secondary' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: query && (
|
||||
<InputAdornment position="end">
|
||||
<Close
|
||||
sx={{ cursor: 'pointer', color: 'text.secondary' }}
|
||||
onClick={() => setQuery('')}
|
||||
/>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
width: '100%',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
fontSize: '0.95rem',
|
||||
'@media (max-width: 600px)': {
|
||||
fontSize: '1rem' // Larger on mobile to avoid zoom
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{isOpen && results.length > 0 && (
|
||||
<Paper
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 10,
|
||||
mt: 1,
|
||||
maxHeight: 300,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{results.map((result, idx) => (
|
||||
<ListItem key={idx} disablePadding>
|
||||
<ListItemButton onClick={() => handleSelect(result)}>
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight={500}>
|
||||
{result.reference}
|
||||
</Typography>
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user