fix: improve Bible reader loading UX with skeleton states
- Remove full-screen loading backdrop that hides entire UI - Add skeleton loading components for chapter headers and verses - Implement smooth content transitions without UI disappearance - Change initial loading state to prevent immediate UI hide - Enhance Suspense fallbacks with better loading messages - Keep Bible reader interface visible during all loading states Fixes issue where: - Entire reader disappeared during chapter changes - Users saw only header/footer during loading - Poor perceived performance with jarring transitions Now provides professional skeleton loading within the reader interface for a smooth, responsive user experience. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -120,8 +120,17 @@ export default async function BibleChapterPage({ params }: PageProps) {
|
|||||||
|
|
||||||
// Pass the parameters as props instead of URLSearchParams
|
// Pass the parameters as props instead of URLSearchParams
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={
|
||||||
<BibleReader
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '200px'
|
||||||
|
}}>
|
||||||
|
Loading Bible reader...
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<BibleReader
|
||||||
initialVersion={resources.versionId}
|
initialVersion={resources.versionId}
|
||||||
initialBook={resources.bookId}
|
initialBook={resources.bookId}
|
||||||
initialChapter={chapter}
|
initialChapter={chapter}
|
||||||
|
|||||||
@@ -50,7 +50,16 @@ export default async function BiblePage({ searchParams, params }: PageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '200px'
|
||||||
|
}}>
|
||||||
|
Loading Bible reader...
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
<BibleReader />
|
<BibleReader />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
|||||||
const [selectedBook, setSelectedBook] = useState<string>('')
|
const [selectedBook, setSelectedBook] = useState<string>('')
|
||||||
const [selectedChapter, setSelectedChapter] = useState<number>(1)
|
const [selectedChapter, setSelectedChapter] = useState<number>(1)
|
||||||
const [verses, setVerses] = useState<BibleVerse[]>([])
|
const [verses, setVerses] = useState<BibleVerse[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(false)
|
||||||
const [versionsLoading, setVersionsLoading] = useState(true)
|
const [versionsLoading, setVersionsLoading] = useState(true)
|
||||||
const [showAllVersions, setShowAllVersions] = useState(false)
|
const [showAllVersions, setShowAllVersions] = useState(false)
|
||||||
|
|
||||||
@@ -1382,13 +1382,7 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (loading && books.length === 0) {
|
// Always render the UI - loading will be handled within components
|
||||||
return (
|
|
||||||
<Backdrop open>
|
|
||||||
<CircularProgress color="inherit" />
|
|
||||||
</Backdrop>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -1480,24 +1474,101 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
|
|||||||
<Box sx={{ opacity: loading ? 0.3 : 1, transition: 'opacity 0.3s ease' }}>
|
<Box sx={{ opacity: loading ? 0.3 : 1, transition: 'opacity 0.3s ease' }}>
|
||||||
{/* Chapter Header */}
|
{/* Chapter Header */}
|
||||||
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
||||||
<Typography
|
{loading && !currentBook ? (
|
||||||
variant="h3"
|
// Skeleton loading for chapter header
|
||||||
component="h1"
|
<>
|
||||||
sx={{
|
<Box
|
||||||
mb: 2,
|
sx={{
|
||||||
fontFamily: preferences.fontFamily === 'serif' ? 'Georgia, serif' : 'Arial, sans-serif'
|
height: 40,
|
||||||
}}
|
backgroundColor: 'action.hover',
|
||||||
>
|
borderRadius: 1,
|
||||||
{currentBook?.name} {selectedChapter}
|
mb: 2,
|
||||||
</Typography>
|
margin: '0 auto',
|
||||||
<Typography variant="body2" color="text.secondary">
|
width: '200px'
|
||||||
{(loading && previousVerses.length > 0 ? previousVerses : verses).length} {t('verses')}
|
}}
|
||||||
</Typography>
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 16,
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
borderRadius: 1,
|
||||||
|
margin: '0 auto',
|
||||||
|
width: '80px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
component="h1"
|
||||||
|
sx={{
|
||||||
|
mb: 2,
|
||||||
|
fontFamily: preferences.fontFamily === 'serif' ? 'Georgia, serif' : 'Arial, sans-serif'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentBook?.name} {selectedChapter}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{(loading && previousVerses.length > 0 ? previousVerses : verses).length} {t('verses')}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Verses */}
|
{/* Verses */}
|
||||||
<Box sx={{ mb: 4 }}>
|
<Box sx={{ mb: 4 }}>
|
||||||
{(loading && previousVerses.length > 0 ? previousVerses : verses).map(renderVerse)}
|
{loading && verses.length === 0 && previousVerses.length === 0 ? (
|
||||||
|
// Skeleton loading for verses
|
||||||
|
<>
|
||||||
|
{Array.from({ length: 8 }).map((_, index) => (
|
||||||
|
<Box key={`skeleton-${index}`} sx={{ mb: 2, display: 'flex', alignItems: 'flex-start' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 20,
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
borderRadius: 1,
|
||||||
|
mr: 2,
|
||||||
|
flexShrink: 0
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 16,
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
borderRadius: 1,
|
||||||
|
mb: 1,
|
||||||
|
width: `${Math.random() * 40 + 60}%`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 16,
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
borderRadius: 1,
|
||||||
|
mb: 1,
|
||||||
|
width: `${Math.random() * 50 + 40}%`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{Math.random() > 0.5 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 16,
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
borderRadius: 1,
|
||||||
|
width: `${Math.random() * 30 + 20}%`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
(loading && previousVerses.length > 0 ? previousVerses : verses).map(renderVerse)
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Chapter Navigation */}
|
{/* Chapter Navigation */}
|
||||||
|
|||||||
Reference in New Issue
Block a user