From 92bc528d0b42914a8f2155cea8c89323dc874c27 Mon Sep 17 00:00:00 2001 From: Dustin Kelley Date: Thu, 21 May 2026 13:29:55 -0500 Subject: [PATCH 1/3] feat(ui): add onFootnotePress callback to BibleCard Expose optional onFootnotePress on BibleCardProps and pass through to BibleTextView so hosts can handle footnotes externally (e.g. RN sheet). Co-authored-by: Cursor --- .changeset/bible-card-footnote-press.md | 4 ++ .../ui/src/components/bible-card.test.tsx | 49 ++++++++++++++++++- packages/ui/src/components/bible-card.tsx | 5 +- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 .changeset/bible-card-footnote-press.md diff --git a/.changeset/bible-card-footnote-press.md b/.changeset/bible-card-footnote-press.md new file mode 100644 index 00000000..cdee58bd --- /dev/null +++ b/.changeset/bible-card-footnote-press.md @@ -0,0 +1,4 @@ +--- +"@youversion/platform-react-ui": patch +--- +Add optional `onFootnotePress` callback to `BibleCard` for custom footnote handling (same pattern as `BibleReader`). diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx index abc86d42..453652be 100644 --- a/packages/ui/src/components/bible-card.test.tsx +++ b/packages/ui/src/components/bible-card.test.tsx @@ -2,8 +2,10 @@ * @vitest-environment jsdom */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { render, act, within } from '@testing-library/react'; +import { render, act, within, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { BibleCard } from './bible-card'; +import type { FootnoteData } from './verse'; import { usePassage, useVersion, useTheme } from '@youversion/platform-react-hooks'; import type { BiblePassage, BibleVersion } from '@youversion/platform-core'; @@ -119,3 +121,48 @@ describe('BibleCard - Delayed spinner', () => { ); }); }); + +describe('BibleCard - onFootnotePress callback', () => { + const mockPassageWithFootnote: BiblePassage = { + id: 'JHN.1', + content: `
5The light shines1:5 Or understood.
`, + reference: 'JHN.1', + }; + + beforeEach(() => { + vi.mocked(useTheme).mockReturnValue('light'); + vi.mocked(useVersion).mockReturnValue({ + version: mockVersion, + loading: false, + error: null, + refetch: vi.fn(), + }); + vi.mocked(usePassage).mockReturnValue({ + passage: mockPassageWithFootnote, + loading: false, + error: null, + refetch: vi.fn(), + }); + }); + + it('should call onFootnotePress when provided via BibleCard', async () => { + const onFootnotePress = vi.fn(); + + const { container } = render( + , + ); + + const button = await waitFor(() => { + const btn = container.querySelector('[data-verse-footnote="5"] button'); + expect(btn).not.toBeNull(); + return btn as HTMLButtonElement; + }); + + await userEvent.click(button); + + expect(onFootnotePress).toHaveBeenCalledTimes(1); + const data = onFootnotePress.mock.calls[0]![0] as FootnoteData; + expect(data.verseNum).toBe('5'); + expect(data.reference).toBe('JHN.1'); + }); +}); diff --git a/packages/ui/src/components/bible-card.tsx b/packages/ui/src/components/bible-card.tsx index 9d5dde6c..a2a52f50 100644 --- a/packages/ui/src/components/bible-card.tsx +++ b/packages/ui/src/components/bible-card.tsx @@ -1,6 +1,6 @@ import { usePassage, useVersion, useTheme } from '@youversion/platform-react-hooks'; import { DEFAULT_LICENSE_FREE_BIBLE_VERSION } from '@youversion/platform-core'; -import { BibleTextView } from './verse'; +import { BibleTextView, type FootnoteData } from './verse'; import { BibleAppLogoLockup } from './bible-app-logo-lockup'; import { BibleVersionPicker, type BibleVersionPickerPressData } from './bible-version-picker'; import { Button } from './ui/button'; @@ -37,6 +37,7 @@ export type BibleCardProps = { background?: 'light' | 'dark'; showVersionPicker?: boolean; onVersionPickerPress?: (data: BibleVersionPickerPressData) => void; + onFootnotePress?: (data: FootnoteData) => void; }; function BibleCardHeaderError(): React.ReactNode { @@ -127,6 +128,7 @@ export function BibleCard({ background, showVersionPicker = false, onVersionPickerPress, + onFootnotePress, }: BibleCardProps): React.ReactNode { // Controlled only when both versionId + onVersionChange are provided. // versionId alone seeds uncontrolled state, preserving backwards compatibility @@ -198,6 +200,7 @@ export function BibleCard({ loading: passageLoading, error: passageError, }} + onFootnotePress={onFootnotePress} /> From bbb65cb2ef23fbb13fa23af59802bb19ce65a6ab Mon Sep 17 00:00:00 2001 From: Dustin Kelley <141975656+Dustin-Kelley@users.noreply.github.com> Date: Thu, 21 May 2026 13:38:36 -0500 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Signed-off-by: Dustin Kelley <141975656+Dustin-Kelley@users.noreply.github.com> --- .changeset/bible-card-footnote-press.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/bible-card-footnote-press.md b/.changeset/bible-card-footnote-press.md index cdee58bd..941b5d17 100644 --- a/.changeset/bible-card-footnote-press.md +++ b/.changeset/bible-card-footnote-press.md @@ -1,4 +1,4 @@ --- -"@youversion/platform-react-ui": patch +"@youversion/platform-react-ui": minor --- Add optional `onFootnotePress` callback to `BibleCard` for custom footnote handling (same pattern as `BibleReader`). From 63ec2620ed50290de4894d23457fe9f27625e60a Mon Sep 17 00:00:00 2001 From: Dustin Kelley Date: Thu, 21 May 2026 13:41:00 -0500 Subject: [PATCH 3/3] fix(ui): address greptile review feedback (greploop iteration 1) Bump changeset to minor for new public API prop and assert notes payload in BibleCard onFootnotePress test. Co-authored-by: Cursor --- packages/ui/src/components/bible-card.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx index 453652be..ca147137 100644 --- a/packages/ui/src/components/bible-card.test.tsx +++ b/packages/ui/src/components/bible-card.test.tsx @@ -164,5 +164,7 @@ describe('BibleCard - onFootnotePress callback', () => { const data = onFootnotePress.mock.calls[0]![0] as FootnoteData; expect(data.verseNum).toBe('5'); expect(data.reference).toBe('JHN.1'); + expect(data.notes).toHaveLength(1); + expect(data.notes[0]).toContain('Or understood'); }); });