Files
biblical-guide.com/components/bible/verse-details-panel.tsx
Andrei 5500965563 fix: add accessibility attributes, display full verse reference, reset tabs on verse change, add character limit
- Add aria-label to close button for screen reader support
- Add dynamic aria-label to bookmark button (Add/Remove bookmark)
- Add aria-label and character counter to notes TextField
- Wrap mobile bottom sheet in proper dialog semantics (role="dialog", aria-modal="true")
- Display full verse reference (Book Chapter:Verse) instead of just verse number
- Add useEffect to reset tab to Notes when verse changes for better UX
- Add 500 character limit to notes with visual counter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 20:03:45 +00:00

179 lines
4.6 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Box, Paper, Typography, Tabs, Tab, IconButton, useMediaQuery, useTheme, TextField, Button } from '@mui/material'
import { Close, Bookmark, BookmarkBorder } from '@mui/icons-material'
import { BibleVerse } from '@/types'
interface VersDetailsPanelProps {
verse: BibleVerse | null
isOpen: boolean
onClose: () => void
isBookmarked: boolean
onToggleBookmark: () => void
onAddNote: (note: string) => void
}
export function VersDetailsPanel({
verse,
isOpen,
onClose,
isBookmarked,
onToggleBookmark,
onAddNote,
}: VersDetailsPanelProps) {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const [tabValue, setTabValue] = useState(0)
const [noteText, setNoteText] = useState('')
// Reset to Notes tab when verse changes
useEffect(() => {
setTabValue(0)
}, [verse?.id])
if (!verse || !isOpen) return null
const handleAddNote = () => {
if (noteText.trim()) {
onAddNote(noteText)
setNoteText('')
}
}
const PanelContent = (
<Box sx={{ p: 2 }}>
{/* Verse Header */}
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'start' }}>
<Typography variant="subtitle1" fontWeight={600} id="verse-details-header">
{verse.chapter?.book?.name} {verse.chapter?.chapterNum}:{verse.verseNum}
</Typography>
<IconButton
size="small"
onClick={onClose}
aria-label="Close verse details"
>
<Close />
</IconButton>
</Box>
{/* Verse Text */}
<Paper sx={{ p: 2, mb: 2, bgcolor: 'grey.100' }} elevation={0}>
<Typography variant="body2" sx={{ mb: 1, fontStyle: 'italic' }}>
{verse.text}
</Typography>
</Paper>
{/* Bookmark Button */}
<Box sx={{ mb: 2 }}>
<Button
aria-label={isBookmarked ? 'Remove bookmark' : 'Add bookmark'}
startIcon={isBookmarked ? <Bookmark /> : <BookmarkBorder />}
onClick={onToggleBookmark}
variant={isBookmarked ? 'contained' : 'outlined'}
size="small"
fullWidth={isMobile}
>
{isBookmarked ? 'Bookmarked' : 'Bookmark'}
</Button>
</Box>
{/* Tabs */}
<Tabs
value={tabValue}
onChange={(_, newValue) => setTabValue(newValue)}
variant={isMobile ? 'fullWidth' : 'standard'}
sx={{ borderBottom: 1, borderColor: 'divider' }}
>
<Tab label="Notes" />
<Tab label="Highlights" />
<Tab label="References" />
</Tabs>
{/* Tab Content */}
<Box sx={{ pt: 2 }}>
{tabValue === 0 && (
<Box>
<TextField
fullWidth
multiline
rows={3}
placeholder="Add a note..."
aria-label="Note text"
helperText={`${noteText.length}/500 characters`}
inputProps={{ maxLength: 500 }}
value={noteText}
onChange={(e) => setNoteText(e.target.value)}
size="small"
sx={{ mb: 1 }}
/>
<Button
variant="contained"
size="small"
onClick={handleAddNote}
disabled={!noteText.trim()}
>
Save Note
</Button>
</Box>
)}
{tabValue === 1 && (
<Typography variant="body2" color="text.secondary">
Highlight colors coming soon
</Typography>
)}
{tabValue === 2 && (
<Typography variant="body2" color="text.secondary">
Cross-references coming soon
</Typography>
)}
</Box>
</Box>
)
if (isMobile) {
return (
<Box
role="dialog"
aria-modal="true"
aria-labelledby="verse-details-header"
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 100,
maxHeight: '70vh',
backgroundColor: 'white',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
boxShadow: '0 -4px 20px rgba(0,0,0,0.1)',
overflow: 'auto',
}}
>
{PanelContent}
</Box>
)
}
return (
<Paper
sx={{
position: 'fixed',
right: 0,
top: 0,
bottom: 0,
width: 350,
zIndex: 100,
borderRadius: 0,
boxShadow: '-4px 0 20px rgba(0,0,0,0.1)',
overflow: 'auto',
backgroundColor: 'white',
}}
>
{PanelContent}
</Paper>
)
}