// Inline study panel shown under a verse in the Reader: commentary from several // public-domain / open sources, plus the people, places and events tied to the verse. import { useState } from 'react'; import { useApp } from '../context/AppContext.js'; import { useIqAvailable } from '../hooks/useIq.js'; import IqCommentary from './iq/IqCommentary.jsx'; import { COMMENTARIES, loadCommentary, loadTheographic, loadPerson, pickCommentaryEntry, useAsync, } from '../hooks/useVerseStudy.js'; export function CommentaryTab({ book, chapter, verse }) { const iq = useIqAvailable(); const [more, setMore] = useState(() => { try { return localStorage.getItem('iq-commentary-mode') === '1'; } catch { return false; } }); const setMode = (on) => { setMore(on); try { localStorage.setItem('iq-commentary-mode', on ? '1' : '0'); } catch { /* storage unavailable */ } }; const chip = (on) => `rounded-full border px-2.5 py-0.5 text-[11px] ${on ? 'border-[var(--r-text,#334155)] bg-[var(--r-text,#334155)] text-[var(--r-bg,#ffffff)]' : 'border-[var(--r-line,#cbd5e1)] text-[var(--r-sub,#64748b)]'}`; return (
{iq && (
)} {iq && more ? : }
); } function ClassicCommentary({ book, chapter, verse }) { // The chosen commentary is a synced preference, shared with the Study stage const { readerCommentary: sourceId, setReaderCommentary: changeSource } = useApp(); const source = COMMENTARIES.find((c) => c.id === sourceId) ?? COMMENTARIES[0]; const { status, data } = useAsync(() => loadCommentary(source.id, book, chapter), [source.id, book, chapter]); const entry = pickCommentaryEntry(data, verse); return (
{status === 'loading' &&

Loading…

} {status === 'empty' &&

{source.name} has no notes for this chapter.

} {status === 'ready' && !entry &&

No notes at or before this verse in {source.name}.

} {status === 'ready' && entry && ( <> {entry.number !== verse && (

Notes on the passage starting at verse {entry.number}.

)}
{entry.text}
)}

{source.credit}

); } function PersonDetails({ id }) { const { status, data } = useAsync(() => loadPerson(id), [id]); if (status === 'loading') return

Loading…

; if (status === 'empty') return

No details available.

; const era = (y) => `${Math.abs(y)}${y < 0 ? ' BC' : ' AD'}`; return (
{(data.description ?? []).map((d, i) =>

{d}

)} {!data.description?.length &&

No description available.

} {data.minYear != null && (

Lived approx. {era(data.minYear)}{data.maxYear != null && data.maxYear !== data.minYear ? ` – ${era(data.maxYear)}` : ''}

)}
); } export function PersonCard({ person }) { const [open, setOpen] = useState(false); return (
  • {open && }
  • ); } export function PeoplePlacesTab({ book, chapter, verse }) { const { status, data } = useAsync(() => loadTheographic(book, chapter), [book, chapter]); if (status === 'loading') return

    Loading…

    ; if (status === 'empty') return

    No people or places data for this chapter.

    ; const here = (list) => list.filter((x) => x.verses?.includes(verse)); // Theographic lists some places twice (variant spellings) — collapse by name. const places = [...new Map(here(data.places).map((p) => [p.name, p])).values()]; const people = here(data.people); const events = here(data.events); if (!people.length && !places.length && !events.length) { return

    Nothing tagged on verse {verse}.

    ; } return (
    {people.length > 0 && (

    People

    )} {places.length > 0 && (

    Places

    )} {events.length > 0 && (

    Events

    )}

    People, places and events: Theographic Bible Metadata, licensed CC BY-SA 4.0.

    ); } export default function StudyPanel({ book, chapter, verse, onClose }) { const [tab, setTab] = useState('commentary'); const tabBtn = (id, label) => ( ); return (
    {tabBtn('commentary', 'Commentary')} {tabBtn('people', 'People & Places')}
    {tab === 'commentary' ? : }
    ); }