Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@youversion/platform-react-ui": minor
---

Add `onVersionPickerPress` escape-hatch prop to `BibleReader.Root` and `BibleCard`, and make `BibleCard.versionId` controllable.

- `BibleReader.Root` accepts `onVersionPickerPress?: (data: BibleVersionPickerPressData) => void`, threaded through context to Toolbar and then to the internal `BibleVersionPicker.Root` — suppresses the default popover when provided
- `BibleCard` accepts `onVersionPickerPress`, `defaultVersionId`, and `onVersionChange`; `versionId` is now optional and uses `useControllableState` for controlled/uncontrolled support
- `BibleVersionPicker.Root` guards `isPopoverOpen` state when escape hatch is active, moves `filteredRecentVersions` to context to eliminate duplication between `Content` and `BibleVersionPickerLanguageTrigger`
- `BibleChapterPicker.Root` guards `isPopoverOpen` state when `onChapterPickerPress` is active
- `BibleWidgetView` kept as a deprecated alias for `BibleCard`
- `BibleVersionPickerPressData` type exported: `{ versionId: number; languageId: string }`
31 changes: 26 additions & 5 deletions packages/ui/src/components/bible-card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { usePassage, useVersion, useTheme } from '@youversion/platform-react-hooks';
import { DEFAULT_LICENSE_FREE_BIBLE_VERSION } from '@youversion/platform-core';
import { BibleTextView } from './verse';
import { BibleAppLogoLockup } from './bible-app-logo-lockup';
import { BibleVersionPicker } from './bible-version-picker';
import { BibleVersionPicker, type BibleVersionPickerPressData } from './bible-version-picker';
import { Button } from './ui/button';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useControllableState } from '@radix-ui/react-use-controllable-state';
import { SOURCE_SERIF_FONT } from '@/lib/verse-html-utils';
import { LoaderIcon } from './icons/loader';
import { AnimatedHeight } from './animated-height';
Expand All @@ -29,9 +31,12 @@ type VersionResult = ReturnType<typeof useVersion>;

export type BibleCardProps = {
reference: string;
versionId: number;
versionId?: number;
defaultVersionId?: number;
onVersionChange?: (versionId: number) => void;
background?: 'light' | 'dark';
showVersionPicker?: boolean;
onVersionPickerPress?: (data: BibleVersionPickerPressData) => void;
};

function BibleCardHeaderError(): React.ReactNode {
Expand Down Expand Up @@ -62,16 +67,19 @@ function BibleCardVersionPicker({
versionId,
onVersionChange,
theme,
onVersionPickerPress,
}: {
versionId: number;
onVersionChange: (id: number) => void;
theme: 'light' | 'dark';
onVersionPickerPress?: (data: BibleVersionPickerPressData) => void;
}): React.ReactNode {
return (
<BibleVersionPicker.Root
onVersionChange={onVersionChange}
versionId={versionId}
background={theme}
onVersionPickerPress={onVersionPickerPress}
>
<BibleVersionPicker.Trigger aria-label="Change Bible version">
{({ version, loading }) => (
Expand Down Expand Up @@ -113,11 +121,23 @@ function BibleCardFooter({ copyright }: { copyright?: string | null }): React.Re

export function BibleCard({
reference,
versionId,
versionId: controlledVersionId,
defaultVersionId = DEFAULT_LICENSE_FREE_BIBLE_VERSION,
onVersionChange,
background,
showVersionPicker = false,
onVersionPickerPress,
}: BibleCardProps): React.ReactNode {
const [versionNum, setVersionNum] = useState(versionId);
// Controlled only when both versionId + onVersionChange are provided.
// versionId alone seeds uncontrolled state, preserving backwards compatibility
// with consumers who use the version picker without an onChange handler.
const isControlled = controlledVersionId !== undefined && onVersionChange !== undefined;

const [versionNum, setVersionNum] = useControllableState({
prop: isControlled ? controlledVersionId : undefined,
defaultProp: isControlled ? defaultVersionId : (controlledVersionId ?? defaultVersionId),
onChange: onVersionChange,
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.
const { version } = useVersion(versionNum);
const {
passage,
Expand Down Expand Up @@ -161,6 +181,7 @@ export function BibleCard({
versionId={versionNum}
onVersionChange={setVersionNum}
theme={theme}
onVersionPickerPress={onVersionPickerPress}
/>
) : null}
</div>
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/components/bible-chapter-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ function Root({
const providerTheme = useTheme();
const theme = background || providerTheme;

const [isPopoverOpen, setIsPopoverOpenRaw] = useState(false);
const [isPopoverOpenRaw, setIsPopoverOpenRaw] = useState(false);
const isPopoverOpen = onChapterPickerPress ? false : isPopoverOpenRaw;
const [searchQuery, setSearchQuery] = useState('');
const [expandedBook, setExpandedBook] = useState<string | null>(book || null);

Expand Down Expand Up @@ -167,6 +168,7 @@ function Root({
}, [expandedBook]);

const setIsPopoverOpen = (open: boolean) => {
if (onChapterPickerPress) return;
setIsPopoverOpenRaw(open);
if (!open) {
setSearchQuery('');
Expand Down
8 changes: 7 additions & 1 deletion packages/ui/src/components/bible-reader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { cn } from '@/lib/utils';
import { DEFAULT_LICENSE_FREE_BIBLE_VERSION, getAdjacentChapter } from '@youversion/platform-core';
import { BibleChapterPicker, type BibleChapterPickerPressData } from './bible-chapter-picker';
import { BibleVersionPicker } from './bible-version-picker';
import { BibleVersionPicker, type BibleVersionPickerPressData } from './bible-version-picker';
import { GearIcon } from './icons/gear';
import { InfoIcon } from './icons/info';
import { LoaderIcon } from './icons/loader';
Expand Down Expand Up @@ -52,6 +52,7 @@ type BibleReaderContextType = {
background: 'light' | 'dark';
onFootnotePress?: (data: FootnoteData) => void;
onChapterPickerPress?: (data: BibleChapterPickerPressData) => void;
onVersionPickerPress?: (data: BibleVersionPickerPressData) => void;
};

const BibleReaderContext = createContext<BibleReaderContextType | null>(null);
Expand Down Expand Up @@ -85,6 +86,7 @@ export type RootProps = {
background?: 'light' | 'dark';
onFootnotePress?: (data: FootnoteData) => void;
onChapterPickerPress?: (data: BibleChapterPickerPressData) => void;
onVersionPickerPress?: (data: BibleVersionPickerPressData) => void;
children?: ReactNode;
};

Expand Down Expand Up @@ -184,6 +186,7 @@ function Root({
background,
onFootnotePress,
onChapterPickerPress,
onVersionPickerPress,
children,
}: RootProps) {
const [book, setBook] = useControllableState({
Expand Down Expand Up @@ -290,6 +293,7 @@ function Root({
background: theme,
onFootnotePress,
onChapterPickerPress,
onVersionPickerPress,
};

return (
Expand Down Expand Up @@ -557,6 +561,7 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
setCurrentFontSize,
background,
onChapterPickerPress,
onVersionPickerPress,
} = useBibleReaderContext();
const yvContext = useContext(YouVersionContext);
const themesSettingsValuesRef = useRef<BibleThemeSettingsValues>({
Expand Down Expand Up @@ -711,6 +716,7 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
versionId={versionId}
onVersionChange={setVersionId}
background={background}
onVersionPickerPress={onVersionPickerPress}
>
<BibleVersionPicker.Trigger aria-label="Change Bible version">
{({ version, loading }) => (
Expand Down
51 changes: 24 additions & 27 deletions packages/ui/src/components/bible-version-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ type BibleVersionPickerContextType = {
setSearchQuery: (query: string) => void;
suggestedLanguages: Pick<Language, 'id' | 'display_names'>[];
filteredVersions: BibleVersion[];
filteredRecentVersions: RecentVersion[];
isLanguagesOpen: boolean;
setIsLanguagesOpen: (open: boolean) => void;
recentVersions: RecentVersion[];
Expand Down Expand Up @@ -232,17 +233,19 @@ function Root({
const [searchQuery, setSearchQuery] = useState('');
const [isLanguagesOpen, setIsLanguagesOpen] = useState(false);
const [recentVersions, setRecentVersions] = useState<RecentVersion[]>(getRecentVersions);
const [isPopoverOpen, setIsPopoverOpenRaw] = useState(false);
const [isPopoverOpenRaw, setIsPopoverOpenRaw] = useState(false);
const isPopoverOpen = onVersionPickerPress ? false : isPopoverOpenRaw;

const setIsPopoverOpen = useCallback(
(open: boolean) => {
if (onVersionPickerPress) return;
setIsPopoverOpenRaw(open);
if (!open) {
setSearchQuery('');
setIsLanguagesOpen(false);
}
},
[setSearchQuery],
[setSearchQuery, onVersionPickerPress],
);

const addRecentVersion = useCallback((version: RecentVersion) => {
Expand Down Expand Up @@ -271,6 +274,17 @@ function Root({
recentVersions,
);

const filteredRecentVersions = useMemo(() => {
if (!searchQuery.trim()) return recentVersions;
const query = searchQuery.trim().toLowerCase();
return recentVersions.filter(
(v) =>
v.title?.toLowerCase().includes(query) ||
v.localized_abbreviation?.toLowerCase().includes(query) ||
v.abbreviation?.toLowerCase().includes(query),
);
}, [recentVersions, searchQuery]);

const getLanguageDisplayName = useCallback(
(language: Pick<Language, 'id' | 'display_names'>) => {
return (
Expand Down Expand Up @@ -345,6 +359,7 @@ function Root({
setSearchQuery,
suggestedLanguages,
filteredVersions,
filteredRecentVersions,
isLanguagesOpen,
setIsLanguagesOpen,
recentVersions,
Expand Down Expand Up @@ -444,22 +459,11 @@ export function BibleVersionPickerLanguageTrigger({
}: BibleVersionPickerLanguageTriggerProps): React.ReactElement {
const {
filteredVersions,
filteredRecentVersions,
setIsLanguagesOpen,
selectedLanguageId,
recentVersions,
searchQuery,
versionsLoading,
} = useBibleVersionPickerContext();
const filteredRecentVersions = useMemo(() => {
if (!searchQuery.trim()) return recentVersions;
const query = searchQuery.trim().toLowerCase();
return recentVersions.filter(
(v) =>
v.title?.toLowerCase().includes(query) ||
v.localized_abbreviation?.toLowerCase().includes(query) ||
v.abbreviation?.toLowerCase().includes(query),
);
}, [recentVersions, searchQuery]);
// Fetch the selected language details (may not be in the paginated languages list)
const { language: selectedLanguage } = useLanguage(selectedLanguageId);

Expand Down Expand Up @@ -505,9 +509,9 @@ function Content({ open, onRequestClose }: BibleVersionPickerContentProps = {})
searchQuery,
setSearchQuery,
filteredVersions,
filteredRecentVersions,
versionId,
setVersionId,
recentVersions,
addRecentVersion,
versionsLoading,
background,
Expand All @@ -519,17 +523,6 @@ function Content({ open, onRequestClose }: BibleVersionPickerContentProps = {})
} = useBibleVersionPickerContext();
const wasOpenRef = useRef(open ?? false);

const filteredRecentVersions = useMemo(() => {
if (!searchQuery.trim()) return recentVersions;
const query = searchQuery.trim().toLowerCase();
return recentVersions.filter(
(v) =>
v.title?.toLowerCase().includes(query) ||
v.localized_abbreviation?.toLowerCase().includes(query) ||
v.abbreviation?.toLowerCase().includes(query),
);
}, [recentVersions, searchQuery]);

const handleSelectVersion = (version: BibleVersion | RecentVersion) => {
setVersionId(version.id);
addRecentVersion({
Expand All @@ -550,7 +543,11 @@ function Content({ open, onRequestClose }: BibleVersionPickerContentProps = {})
wasOpenRef.current = open ?? false;
}, [open, setIsLanguagesOpen, setSearchQuery]);

if (!onVersionPickerPress && open === undefined && !onRequestClose) {
if (onVersionPickerPress && open === undefined && !onRequestClose) {
return null;
}

if (open === undefined && !onRequestClose) {
return (
<PopoverContent
sideOffset={16}
Expand Down
Loading