Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bible-reader-chapter-loading-overlay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@youversion/platform-react-ui': patch
---

Improve the chapter-change loading experience in `BibleReader`/`BibleTextView`. Instead of pulsing the previous chapter's text while the next chapter loads, the outgoing text now fades to a low opacity in place (no layout shift) with a spinner overlaid and centered in the visible reading area. This avoids both the confusing stale-but-active text and the flash-of-empty-content a bare spinner would cause, and the opacity transition doubles as a smooth fade-in for the incoming chapter.
2 changes: 1 addition & 1 deletion packages/ui/src/components/verse.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ describe('BibleTextView - Refetch loading behavior', () => {
);

await waitFor(() => {
const wrapper = container.querySelector('[data-yv-sdk]');
const wrapper = container.querySelector('[aria-busy="true"]');
expect(wrapper).not.toBeNull();
expect((wrapper as HTMLElement).style.pointerEvents).toBe('none');
});
Expand Down
62 changes: 40 additions & 22 deletions packages/ui/src/components/verse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -544,29 +544,47 @@ export const BibleTextView = forwardRef<HTMLDivElement, BibleTextViewProps>(
);
}

// While the next chapter loads, usePassage keeps returning the previous
// passage. Fade it in place behind a spinner rather than clearing it (which
// would flash the layout). The spinner is sticky so it stays centered in view
// on long chapters.
return (
<div
data-yv-sdk
data-yv-theme={currentTheme}
className={cn(fetchedLoading || currentLoading ? 'yv:animate-pulse' : '')}
aria-busy={currentLoading || undefined}
style={currentLoading ? { pointerEvents: 'none' } : undefined}
>
<Verse.Html
ref={ref}
html={currentPassage?.content || ''}
fontFamily={fontFamily}
fontSize={fontSize}
lineHeight={lineHeight}
showVerseNumbers={showVerseNumbers}
renderNotes={renderNotes}
reference={currentPassage?.reference}
theme={currentTheme}
selectedVerses={selectedVerses}
onVerseSelect={onVerseSelect}
highlightedVerses={highlightedVerses}
onFootnotePress={onFootnotePress}
/>
<div data-yv-sdk data-yv-theme={currentTheme} className="yv:relative">
<div
className={cn('yv:transition-opacity yv:duration-300', currentLoading && 'yv:opacity-40')}
aria-busy={currentLoading || undefined}
style={currentLoading ? { pointerEvents: 'none' } : undefined}
>
<Verse.Html
ref={ref}
html={currentPassage?.content || ''}
fontFamily={fontFamily}
fontSize={fontSize}
lineHeight={lineHeight}
showVerseNumbers={showVerseNumbers}
renderNotes={renderNotes}
reference={currentPassage?.reference}
theme={currentTheme}
selectedVerses={selectedVerses}
onVerseSelect={onVerseSelect}
highlightedVerses={highlightedVerses}
onFootnotePress={onFootnotePress}
/>
</div>
{currentLoading && (
<div
role="status"
aria-label={t('loadingPassageAriaLabel')}
className="yv:pointer-events-none yv:absolute yv:inset-0"
>
<div className="yv:sticky yv:top-1/2 yv:flex yv:-translate-y-1/2 yv:justify-center">
<LoaderIcon
className="yv:size-8 yv:animate-spin yv:text-muted-foreground"
Comment on lines +580 to +582
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Sticky spinner invisible on long chapters

yv:top-1/2 resolves top: 50% against the sticky element's containing block β€” the absolute inset-0 overlay div β€” whose height equals the entire chapter text height. For any chapter taller than the viewport (e.g. Psalm 119, long Gospels), that threshold exceeds the viewport height, so position: sticky pins the spinner below the visible area immediately. Users see only the faded text with no visible loading indicator throughout the scroll.

yv:top-[50vh] fixes this β€” it resolves against the viewport height rather than the chapter height, keeping the spinner at the vertical midpoint of the visible area on chapters of any length.

Suggested change
<div className="yv:sticky yv:top-1/2 yv:flex yv:-translate-y-1/2 yv:justify-center">
<LoaderIcon
className="yv:size-8 yv:animate-spin yv:text-muted-foreground"
<div className="yv:sticky yv:top-[50vh] yv:flex yv:-translate-y-1/2 yv:justify-center">
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/components/verse.tsx
Line: 580-582

Comment:
**Sticky spinner invisible on long chapters**

`yv:top-1/2` resolves `top: 50%` against the sticky element's *containing block* β€” the `absolute inset-0` overlay div β€” whose height equals the entire chapter text height. For any chapter taller than the viewport (e.g. Psalm 119, long Gospels), that threshold exceeds the viewport height, so `position: sticky` pins the spinner below the visible area immediately. Users see only the faded text with no visible loading indicator throughout the scroll.

`yv:top-[50vh]` fixes this β€” it resolves against the viewport height rather than the chapter height, keeping the spinner at the vertical midpoint of the visible area on chapters of any length.

```suggestion
            <div className="yv:sticky yv:top-[50vh] yv:flex yv:-translate-y-1/2 yv:justify-center">
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

aria-hidden="true"
/>
</div>
</div>
)}
</div>
);
},
Expand Down
Loading