Skip to content

Commit 297ffa4

Browse files
committed
feat(ui): add dark mode to footnotes
1 parent 8807d08 commit 297ffa4

File tree

4 files changed

+157
-2
lines changed

4 files changed

+157
-2
lines changed

packages/ui/src/components/bible-reader.stories.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,55 @@ export const FootnotesPersistAfterFontSizeChange: Story = {
288288
});
289289
},
290290
};
291+
292+
export const ThemeOverridesProvider: Story = {
293+
tags: ['integration'],
294+
args: {
295+
versionId: 111,
296+
book: 'JHN',
297+
chapter: '1',
298+
background: 'light',
299+
},
300+
globals: {
301+
theme: 'dark',
302+
},
303+
render: (args) => (
304+
<div className="yv:h-screen yv:bg-background">
305+
<BibleReader.Root {...args}>
306+
<BibleReader.Content />
307+
<BibleReader.Toolbar />
308+
</BibleReader.Root>
309+
</div>
310+
),
311+
play: async ({ canvasElement }) => {
312+
await waitFor(
313+
async () => {
314+
const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]');
315+
await expect(verseContainer).toBeInTheDocument();
316+
},
317+
{ timeout: 5000 },
318+
);
319+
320+
const readerTheme = canvasElement.querySelector('[data-yv-theme="light"]');
321+
await expect(readerTheme).toBeInTheDocument();
322+
323+
await waitFor(
324+
async () => {
325+
const footnoteButton = canvasElement.querySelector('[data-verse-footnote] button');
326+
await expect(footnoteButton).toBeInTheDocument();
327+
},
328+
{ timeout: 5000 },
329+
);
330+
331+
const footnoteButton = canvasElement.querySelector('[data-verse-footnote] button');
332+
await expect(footnoteButton?.closest('[data-yv-theme="light"]')).toBeInTheDocument();
333+
334+
await userEvent.click(footnoteButton!);
335+
336+
await waitFor(async () => {
337+
const popover = document.querySelector('[data-slot="popover-content"]');
338+
await expect(popover).toBeInTheDocument();
339+
await expect(popover?.closest('[data-yv-theme="light"]')).toBeInTheDocument();
340+
});
341+
},
342+
};

packages/ui/src/components/bible-reader.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ function Root({
151151

152152
function Content() {
153153
const {
154+
background,
154155
book,
155156
chapter,
156157
versionId,
@@ -191,6 +192,7 @@ function Content() {
191192
fontSize={currentFontSize}
192193
lineHeight={lineHeight}
193194
showVerseNumbers={showVerseNumbers}
195+
theme={background}
194196
/>
195197

196198
{version?.copyright && (

packages/ui/src/components/verse.stories.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,86 @@ export const FootnoteInteraction: Story = {
226226
});
227227
},
228228
};
229+
230+
export const FootnotePopoverThemeLight: Story = {
231+
args: {
232+
reference: 'JHN.1',
233+
versionId: 111,
234+
renderNotes: true,
235+
showVerseNumbers: true,
236+
theme: 'light',
237+
},
238+
tags: ['integration'],
239+
play: async ({ canvasElement }) => {
240+
await waitFor(
241+
async () => {
242+
const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]');
243+
await expect(verseContainer).toBeInTheDocument();
244+
},
245+
{ timeout: 5000 },
246+
);
247+
248+
await waitFor(
249+
async () => {
250+
const footnoteButton = canvasElement.querySelector('[data-verse-footnote] button');
251+
await expect(footnoteButton).toBeInTheDocument();
252+
},
253+
{ timeout: 5000 },
254+
);
255+
256+
const footnoteButton = canvasElement.querySelector('[data-verse-footnote] button');
257+
await expect(footnoteButton?.closest('[data-yv-theme="light"]')).toBeInTheDocument();
258+
259+
await userEvent.click(footnoteButton!);
260+
261+
await waitFor(async () => {
262+
const popover = document.querySelector('[data-slot="popover-content"]');
263+
await expect(popover).toBeInTheDocument();
264+
await expect(popover?.closest('[data-yv-theme="light"]')).toBeInTheDocument();
265+
});
266+
},
267+
};
268+
269+
export const FootnotePopoverThemeDark: Story = {
270+
args: {
271+
reference: 'JHN.1',
272+
versionId: 111,
273+
renderNotes: true,
274+
showVerseNumbers: true,
275+
theme: 'dark',
276+
},
277+
tags: ['integration'],
278+
render: (args) => (
279+
<div className="yv:dark">
280+
<BibleTextView {...args} />
281+
</div>
282+
),
283+
play: async ({ canvasElement }) => {
284+
await waitFor(
285+
async () => {
286+
const verseContainer = canvasElement.querySelector('[data-slot="yv-bible-renderer"]');
287+
await expect(verseContainer).toBeInTheDocument();
288+
},
289+
{ timeout: 5000 },
290+
);
291+
292+
await waitFor(
293+
async () => {
294+
const footnoteButton = canvasElement.querySelector('[data-verse-footnote] button');
295+
await expect(footnoteButton).toBeInTheDocument();
296+
},
297+
{ timeout: 5000 },
298+
);
299+
300+
const footnoteButton = canvasElement.querySelector('[data-verse-footnote] button');
301+
await expect(footnoteButton?.closest('[data-yv-theme="dark"]')).toBeInTheDocument();
302+
303+
await userEvent.click(footnoteButton!);
304+
305+
await waitFor(async () => {
306+
const popover = document.querySelector('[data-slot="popover-content"]');
307+
await expect(popover).toBeInTheDocument();
308+
await expect(popover?.closest('[data-yv-theme="dark"]')).toBeInTheDocument();
309+
});
310+
},
311+
};

packages/ui/src/components/verse.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from 'react';
1212
import { createPortal } from 'react-dom';
1313
import DOMPurify from 'isomorphic-dompurify';
14-
import { usePassage } from '@youversion/platform-react-hooks';
14+
import { usePassage, useTheme } from '@youversion/platform-react-hooks';
1515
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
1616
import { Footnote } from './icons/footnote';
1717

@@ -163,16 +163,18 @@ const VerseFootnoteButton = memo(function VerseFootnoteButton({
163163
verseNotes,
164164
reference,
165165
fontSize,
166+
theme,
166167
}: {
167168
verseNum: string;
168169
verseNotes: VerseNotes;
169170
reference?: string;
170171
fontSize?: number;
172+
theme: 'light' | 'dark';
171173
}) {
172174
const verseReference = reference ? `${reference}:${verseNum}` : `Verse ${verseNum}`;
173175
return (
174176
<Popover>
175-
<PopoverTrigger data-yv-sdk asChild>
177+
<PopoverTrigger data-yv-sdk data-yv-theme={theme} asChild>
176178
<button
177179
type="button"
178180
className="yv:inline-flex yv:align-middle yv:cursor-pointer yv:ml-1! yv:text-(--yv-gray-20)"
@@ -183,6 +185,7 @@ const VerseFootnoteButton = memo(function VerseFootnoteButton({
183185
<PopoverContent
184186
className="yv:flex yv:flex-col yv:bg-background yv:p-0 yv:sm:w-sm yv:overflow-none yv:rounded-2xl yv:border-0 yv:shadow-lg"
185187
heading="Footnotes"
188+
theme={theme}
186189
>
187190
<div className="yv:p-3 yv:overflow-y-auto yv:max-h-[33svh]">
188191
<div className="yv:font-bold yv:mb-2">{verseReference}</div>
@@ -213,14 +216,18 @@ function HtmlWithNotes({
213216
notes,
214217
reference,
215218
fontSize,
219+
theme,
216220
}: {
217221
html: string;
218222
notes: Record<string, VerseNotes>;
219223
reference?: string;
220224
fontSize?: number;
225+
theme?: 'light' | 'dark';
221226
}) {
222227
const contentRef = useRef<HTMLDivElement>(null);
223228
const [placeholders, setPlaceholders] = useState<Map<string, Element>>(new Map());
229+
const providerTheme = useTheme();
230+
const currentTheme = theme || providerTheme;
224231

225232
useLayoutEffect(() => {
226233
if (!contentRef.current) return;
@@ -246,6 +253,7 @@ function HtmlWithNotes({
246253
verseNotes={verseNotes}
247254
reference={reference}
248255
fontSize={fontSize}
256+
theme={currentTheme}
249257
/>,
250258
el,
251259
);
@@ -362,6 +370,7 @@ type VerseHtmlProps = {
362370
showVerseNumbers?: boolean;
363371
renderNotes?: boolean;
364372
reference?: string;
373+
theme?: 'light' | 'dark';
365374
};
366375

367376
/**
@@ -409,10 +418,13 @@ export const Verse = {
409418
showVerseNumbers = true,
410419
renderNotes = true,
411420
reference,
421+
theme,
412422
}: VerseHtmlProps,
413423
ref,
414424
): ReactNode => {
415425
const [transformedData, setTransformedData] = useState<ExtractedNotes>({ html, notes: {} });
426+
const providerTheme = useTheme();
427+
const currentTheme = theme || providerTheme;
416428

417429
useEffect(() => {
418430
setTransformedData(yvDomTransformer(html, renderNotes));
@@ -437,6 +449,7 @@ export const Verse = {
437449
notes={transformedData.notes}
438450
reference={reference}
439451
fontSize={fontSize}
452+
theme={currentTheme}
440453
/>
441454
</section>
442455
);
@@ -469,6 +482,7 @@ export type BibleTextViewProps = {
469482
versionId: number;
470483
showVerseNumbers?: boolean;
471484
renderNotes?: boolean;
485+
theme?: 'light' | 'dark';
472486
};
473487

474488
/**
@@ -482,13 +496,16 @@ export const BibleTextView = ({
482496
versionId,
483497
showVerseNumbers,
484498
renderNotes,
499+
theme,
485500
}: BibleTextViewProps): React.ReactElement => {
486501
const { passage, loading, error } = usePassage({
487502
versionId,
488503
usfm: reference,
489504
include_headings: true,
490505
include_notes: true,
491506
});
507+
const providerTheme = useTheme();
508+
const currentTheme = theme || providerTheme;
492509

493510
if (loading) {
494511
return (
@@ -525,6 +542,7 @@ export const BibleTextView = ({
525542
showVerseNumbers={showVerseNumbers}
526543
renderNotes={renderNotes}
527544
reference={passage?.reference}
545+
theme={currentTheme}
528546
/>
529547
);
530548
};

0 commit comments

Comments
 (0)