From 12431702dd9a817f6564ca981e04ebae745ee981 Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Thu, 21 May 2026 11:45:15 -0500 Subject: [PATCH 1/4] fix(ui): center bible card content Remove the outer BibleCard max width while keeping the rendered card content centered at a readable line length. This lets host layouts size the card while preserving balanced whitespace inside wide containers. --- packages/core/src/styles/bible-reader.css | 2 - .../ui/src/components/bible-card.stories.tsx | 44 +++++++++++ .../ui/src/components/bible-card.test.tsx | 20 +++++ packages/ui/src/components/bible-card.tsx | 78 ++++++++++--------- packages/ui/src/components/verse.test.tsx | 29 +++++++ 5 files changed, 133 insertions(+), 40 deletions(-) diff --git a/packages/core/src/styles/bible-reader.css b/packages/core/src/styles/bible-reader.css index 870a662d..73bd1925 100644 --- a/packages/core/src/styles/bible-reader.css +++ b/packages/core/src/styles/bible-reader.css @@ -5,8 +5,6 @@ display: block; width: 100%; - /* This helps readability and is a standard max width for text */ - max-width: 65ch; --yv-reader-font-size: 20px; --yv-reader-line-height: 1.625; diff --git a/packages/ui/src/components/bible-card.stories.tsx b/packages/ui/src/components/bible-card.stories.tsx index cef2605e..4ec445b1 100644 --- a/packages/ui/src/components/bible-card.stories.tsx +++ b/packages/ui/src/components/bible-card.stories.tsx @@ -44,6 +44,50 @@ export const Default: Story = { versionId: 111, }, }; + +export const WideContainer: Story = { + args: { + reference: 'LUK.1.39-45', + versionId: 111, + }, + tags: ['integration'], + parameters: { + layout: 'fullscreen', + }, + render: (args) => ( +
+ +
+ ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await waitFor(async () => { + await expect(canvas.getByText(/at that time mary got ready/i)).toBeInTheDocument(); + }); + + const card = canvasElement.querySelector('section[data-yv-sdk][data-yv-theme]'); + const contentGroup = canvasElement.querySelector('section[data-yv-sdk][data-yv-theme] > div'); + const bibleText = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); + + await expect(card).not.toBeNull(); + await expect(contentGroup).not.toBeNull(); + await expect(bibleText).not.toBeNull(); + + const cardWidth = card?.getBoundingClientRect().width ?? 0; + const contentGroupRect = contentGroup?.getBoundingClientRect(); + const cardRect = card?.getBoundingClientRect(); + const leftWhitespace = (contentGroupRect?.left ?? 0) - (cardRect?.left ?? 0); + const rightWhitespace = (cardRect?.right ?? 0) - (contentGroupRect?.right ?? 0); + const bibleTextWidth = bibleText?.getBoundingClientRect().width ?? 0; + + await expect(cardWidth).toBeGreaterThan(800); + await expect(contentGroupRect?.width ?? 0).toBeLessThanOrEqual(600); + await expect(bibleTextWidth).toBeLessThanOrEqual(600); + await expect(Math.abs(leftWhitespace - rightWhitespace)).toBeLessThanOrEqual(1); + }, +}; + export const DarkMode: Story = { args: { reference: 'LUK.1.39-45', diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx index abc86d42..ab9987bb 100644 --- a/packages/ui/src/components/bible-card.test.tsx +++ b/packages/ui/src/components/bible-card.test.tsx @@ -118,4 +118,24 @@ describe('BibleCard - Delayed spinner', () => { 0, ); }); + + it('should let the card fill its container while centering the content group', () => { + vi.mocked(usePassage).mockReturnValue({ + passage: mockPassage, + loading: false, + error: null, + refetch: vi.fn(), + }); + + const { container } = render(); + const card = container.querySelector('section'); + const contentGroup = container.querySelector('section > div[style*="max-width"]'); + const bibleTextView = container.querySelector('[data-slot="yv-bible-renderer"]')?.parentElement; + + expect(card).toHaveClass('yv:w-full'); + expect(card).not.toHaveClass('yv:max-w-md'); + expect((contentGroup as HTMLElement).style.maxWidth).toBe('600px'); + expect((contentGroup as HTMLElement).style.marginInline).toBe('auto'); + expect(bibleTextView).not.toHaveClass('yv:max-w-[600px]'); + }); }); diff --git a/packages/ui/src/components/bible-card.tsx b/packages/ui/src/components/bible-card.tsx index 9d5dde6c..771aa1f9 100644 --- a/packages/ui/src/components/bible-card.tsx +++ b/packages/ui/src/components/bible-card.tsx @@ -160,48 +160,50 @@ export function BibleCard({
-
- {passage && !passageError ? ( -
- - {showSpinner ? ( - - ) : null} -
- ) : passageError ? ( - - ) : ( - - )} - - {showVersionPicker && !passageError ? ( - +
+ {passage && !passageError ? ( +
+ + {showSpinner ? ( + + ) : null} +
+ ) : passageError ? ( + + ) : ( + + )} + + {showVersionPicker && !passageError ? ( + + ) : null} +
+ + + - ) : null} -
+ - - - - - + +
); } diff --git a/packages/ui/src/components/verse.test.tsx b/packages/ui/src/components/verse.test.tsx index 8db161ae..8efac9b5 100644 --- a/packages/ui/src/components/verse.test.tsx +++ b/packages/ui/src/components/verse.test.tsx @@ -636,6 +636,35 @@ describe('BibleTextView - Refetch loading behavior', () => { }); }); + it('should not set layout width on BibleTextView', async () => { + const { container } = render( + , + ); + + await waitFor(() => { + const wrapper = container.querySelector('[data-yv-sdk]'); + expect((wrapper as HTMLElement).style.width).toBe(''); + expect((wrapper as HTMLElement).style.maxWidth).toBe(''); + }); + }); + + it('should leave Verse.Html without a text width class', async () => { + const { container } = render(); + + await waitFor(() => { + const section = container.querySelector('section[data-slot="yv-bible-renderer"]'); + expect((section as HTMLElement).style.maxWidth).toBe(''); + }); + }); + it('should show spinner on initial load when passage is null', async () => { const { container } = render( Date: Thu, 21 May 2026 12:31:17 -0500 Subject: [PATCH 2/4] fix(ui): center verse card content Center the VerseOfTheDay content within a full-width card and update Storybook framing for realistic width behavior. Keep card shells border-box so full-width cards fit narrow containers without adding minimum widths. --- .../ui/src/components/bible-card.stories.tsx | 4 +- .../ui/src/components/bible-card.test.tsx | 1 + packages/ui/src/components/bible-card.tsx | 1 + .../components/verse-of-the-day.stories.tsx | 51 +++++- .../src/components/verse-of-the-day.test.tsx | 12 ++ .../ui/src/components/verse-of-the-day.tsx | 160 +++++++++--------- 6 files changed, 149 insertions(+), 80 deletions(-) diff --git a/packages/ui/src/components/bible-card.stories.tsx b/packages/ui/src/components/bible-card.stories.tsx index 4ec445b1..5e5ea9b0 100644 --- a/packages/ui/src/components/bible-card.stories.tsx +++ b/packages/ui/src/components/bible-card.stories.tsx @@ -7,10 +7,10 @@ const meta = { title: 'Components/BibleCard', component: BibleCard, parameters: { - layout: 'centered', + layout: 'fullscreen', }, render: (args) => ( -
+
), diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx index ab9987bb..73cd426f 100644 --- a/packages/ui/src/components/bible-card.test.tsx +++ b/packages/ui/src/components/bible-card.test.tsx @@ -134,6 +134,7 @@ describe('BibleCard - Delayed spinner', () => { expect(card).toHaveClass('yv:w-full'); expect(card).not.toHaveClass('yv:max-w-md'); + expect(card!.style.boxSizing).toBe('border-box'); expect((contentGroup as HTMLElement).style.maxWidth).toBe('600px'); expect((contentGroup as HTMLElement).style.marginInline).toBe('auto'); expect(bibleTextView).not.toHaveClass('yv:max-w-[600px]'); diff --git a/packages/ui/src/components/bible-card.tsx b/packages/ui/src/components/bible-card.tsx index 771aa1f9..d9e2d104 100644 --- a/packages/ui/src/components/bible-card.tsx +++ b/packages/ui/src/components/bible-card.tsx @@ -161,6 +161,7 @@ export function BibleCard({ data-yv-sdk data-yv-theme={theme} className="yv:w-full yv:flex yv:flex-col yv:grow yv:bg-card yv:p-6 yv:rounded-2xl" + style={{ boxSizing: 'border-box' }} >
diff --git a/packages/ui/src/components/verse-of-the-day.stories.tsx b/packages/ui/src/components/verse-of-the-day.stories.tsx index a7ed075e..2ee55a71 100644 --- a/packages/ui/src/components/verse-of-the-day.stories.tsx +++ b/packages/ui/src/components/verse-of-the-day.stories.tsx @@ -7,8 +7,13 @@ const meta = { title: 'Components/VerseOfTheDay', component: VerseOfTheDay, parameters: { - layout: 'centered', + layout: 'fullscreen', }, + render: (args) => ( +
+ +
+ ), tags: ['autodocs'], argTypes: { background: { @@ -85,6 +90,50 @@ export const Default: Story = { }, }; +export const WideContainer: Story = { + args: { + versionId: 111, + showSunIcon: true, + showBibleAppAttribution: true, + showShareButton: true, + size: 'default', + }, + tags: ['integration'], + parameters: { + layout: 'fullscreen', + }, + render: (args) => ( +
+ +
+ ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await expect( + await canvas.findByText(/for I am about to do something new/i), + ).toBeInTheDocument(); + + const card = canvasElement.querySelector('section[data-yv-sdk][data-yv-theme]'); + const contentGroup = canvasElement.querySelector('section[data-yv-sdk][data-yv-theme] > div'); + const bibleText = canvasElement.querySelector('[data-slot="yv-bible-renderer"]'); + + await expect(card).not.toBeNull(); + await expect(contentGroup).not.toBeNull(); + await expect(bibleText).not.toBeNull(); + + const cardRect = card?.getBoundingClientRect(); + const contentGroupRect = contentGroup?.getBoundingClientRect(); + const leftWhitespace = (contentGroupRect?.left ?? 0) - (cardRect?.left ?? 0); + const rightWhitespace = (cardRect?.right ?? 0) - (contentGroupRect?.right ?? 0); + + await expect(cardRect?.width ?? 0).toBeGreaterThan(800); + await expect(contentGroupRect?.width ?? 0).toBeLessThanOrEqual(600); + await expect(bibleText?.getBoundingClientRect().width ?? 0).toBeLessThanOrEqual(600); + await expect(Math.abs(leftWhitespace - rightWhitespace)).toBeLessThanOrEqual(1); + }, +}; + export const Large: Story = { args: { size: 'lg', diff --git a/packages/ui/src/components/verse-of-the-day.test.tsx b/packages/ui/src/components/verse-of-the-day.test.tsx index 8471cbc8..237cb818 100644 --- a/packages/ui/src/components/verse-of-the-day.test.tsx +++ b/packages/ui/src/components/verse-of-the-day.test.tsx @@ -70,4 +70,16 @@ describe('VerseOfTheDay i18n integration', () => { render(); expect(screen.getByText(en.verseOfTheDay)).toBeInTheDocument(); }); + + it('lets the card fill its container while centering the content group', () => { + const { container } = render(); + const card = container.querySelector('section'); + const contentGroup = container.querySelector('section > div[style*="max-width"]'); + + expect(card).toHaveClass('yv:w-full'); + expect(card).not.toHaveClass('yv:max-w-md'); + expect(card!.style.boxSizing).toBe('border-box'); + expect((contentGroup as HTMLElement).style.maxWidth).toBe('600px'); + expect((contentGroup as HTMLElement).style.marginInline).toBe('auto'); + }); }); diff --git a/packages/ui/src/components/verse-of-the-day.tsx b/packages/ui/src/components/verse-of-the-day.tsx index cdbf8e8e..93f50184 100644 --- a/packages/ui/src/components/verse-of-the-day.tsx +++ b/packages/ui/src/components/verse-of-the-day.tsx @@ -132,92 +132,98 @@ export function VerseOfTheDay({ data-yv-theme={theme} data-size={size} className={ - 'yv:data-[size=lg]:p-8 yv:data-[size=default]:p-4 yv:*:shrink-0 yv:font-sans yv:flex yv:flex-col yv:gap-3 yv:w-full yv:grow yv:max-w-md yv:p-4 yv:rounded-2xl yv:bg-card' + 'yv:data-[size=lg]:p-8 yv:data-[size=default]:p-4 yv:*:shrink-0 yv:font-sans yv:flex yv:flex-col yv:w-full yv:grow yv:p-4 yv:rounded-2xl yv:bg-card' } + style={{ boxSizing: 'border-box' }} > -
- {showSunIcon ? ( -
- +
+
+ {showSunIcon ? ( +
+ +
+ ) : null} +
+

+ {t('verseOfTheDay')} +

- ) : null} -
-

- {t('verseOfTheDay')} -

+ {showShareButton ? ( +
+ +
+ ) : null}
- {showShareButton ? ( -
- -
- ) : null} -
+
+ ) : ( +
+ - - {isLoading ? ( + {errorPassage || errorVerseOfTheDay ? null : ( +

+ {referenceText} +

+ )} +
+ )} + + + {showBibleAppAttribution ? (
-
- ) : ( -
- - - {errorPassage || errorVerseOfTheDay ? null : ( -

- {referenceText} -

- )} -
- )} - - - {showBibleAppAttribution ? ( -
- -
- ) : null} + ) : null} +
); } From 17b61447bdbae76d86f157ff84310cdbc477c2be Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Thu, 21 May 2026 12:34:57 -0500 Subject: [PATCH 3/4] feat(ui): add card layout changeset Add a minor changeset for the card layout update so the UI package version captures the new full-width centered card behavior. --- .changeset/pretty-bibles-sit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pretty-bibles-sit.md diff --git a/.changeset/pretty-bibles-sit.md b/.changeset/pretty-bibles-sit.md new file mode 100644 index 00000000..0fec41c6 --- /dev/null +++ b/.changeset/pretty-bibles-sit.md @@ -0,0 +1,5 @@ +--- +"@youversion/platform-react-ui": minor +--- + +Center card content within full-width BibleCard and VerseOfTheDay layouts. From 162768275d633be55b419e1c74505a8e4e7c839e Mon Sep 17 00:00:00 2001 From: Cameron Pak Date: Fri, 22 May 2026 10:04:47 -0500 Subject: [PATCH 4/4] fix(ui): address card layout feedback Keep BibleTextView readable by default while moving card layout constraints into Tailwind classes. Remove tests that only checked inline style state instead of real layout behavior. --- packages/core/src/styles/bible-reader.css | 1 + .../ui/src/components/bible-card.test.tsx | 7 ++--- packages/ui/src/components/bible-card.tsx | 5 ++-- .../src/components/verse-of-the-day.test.tsx | 7 ++--- .../ui/src/components/verse-of-the-day.tsx | 8 ++--- packages/ui/src/components/verse.test.tsx | 29 ------------------- packages/ui/src/styles/global.css | 6 ++++ 7 files changed, 17 insertions(+), 46 deletions(-) diff --git a/packages/core/src/styles/bible-reader.css b/packages/core/src/styles/bible-reader.css index 73bd1925..cc0c289b 100644 --- a/packages/core/src/styles/bible-reader.css +++ b/packages/core/src/styles/bible-reader.css @@ -5,6 +5,7 @@ display: block; width: 100%; + max-width: var(--yv-reader-max-width, 65ch); --yv-reader-font-size: 20px; --yv-reader-line-height: 1.625; diff --git a/packages/ui/src/components/bible-card.test.tsx b/packages/ui/src/components/bible-card.test.tsx index 73cd426f..247cbd32 100644 --- a/packages/ui/src/components/bible-card.test.tsx +++ b/packages/ui/src/components/bible-card.test.tsx @@ -129,14 +129,13 @@ describe('BibleCard - Delayed spinner', () => { const { container } = render(); const card = container.querySelector('section'); - const contentGroup = container.querySelector('section > div[style*="max-width"]'); + const contentGroup = container.querySelector('section > div'); const bibleTextView = container.querySelector('[data-slot="yv-bible-renderer"]')?.parentElement; expect(card).toHaveClass('yv:w-full'); expect(card).not.toHaveClass('yv:max-w-md'); - expect(card!.style.boxSizing).toBe('border-box'); - expect((contentGroup as HTMLElement).style.maxWidth).toBe('600px'); - expect((contentGroup as HTMLElement).style.marginInline).toBe('auto'); + expect(card).toHaveClass('yv:box-border'); + expect(contentGroup).toHaveClass('yv:card-content'); expect(bibleTextView).not.toHaveClass('yv:max-w-[600px]'); }); }); diff --git a/packages/ui/src/components/bible-card.tsx b/packages/ui/src/components/bible-card.tsx index d9e2d104..58768e40 100644 --- a/packages/ui/src/components/bible-card.tsx +++ b/packages/ui/src/components/bible-card.tsx @@ -160,10 +160,9 @@ export function BibleCard({
-
+
{passage && !passageError ? (
diff --git a/packages/ui/src/components/verse-of-the-day.test.tsx b/packages/ui/src/components/verse-of-the-day.test.tsx index 237cb818..9af55b83 100644 --- a/packages/ui/src/components/verse-of-the-day.test.tsx +++ b/packages/ui/src/components/verse-of-the-day.test.tsx @@ -74,12 +74,11 @@ describe('VerseOfTheDay i18n integration', () => { it('lets the card fill its container while centering the content group', () => { const { container } = render(); const card = container.querySelector('section'); - const contentGroup = container.querySelector('section > div[style*="max-width"]'); + const contentGroup = container.querySelector('section > div'); expect(card).toHaveClass('yv:w-full'); expect(card).not.toHaveClass('yv:max-w-md'); - expect(card!.style.boxSizing).toBe('border-box'); - expect((contentGroup as HTMLElement).style.maxWidth).toBe('600px'); - expect((contentGroup as HTMLElement).style.marginInline).toBe('auto'); + expect(card).toHaveClass('yv:box-border'); + expect(contentGroup).toHaveClass('yv:card-content'); }); }); diff --git a/packages/ui/src/components/verse-of-the-day.tsx b/packages/ui/src/components/verse-of-the-day.tsx index 93f50184..0744531f 100644 --- a/packages/ui/src/components/verse-of-the-day.tsx +++ b/packages/ui/src/components/verse-of-the-day.tsx @@ -132,14 +132,10 @@ export function VerseOfTheDay({ data-yv-theme={theme} data-size={size} className={ - 'yv:data-[size=lg]:p-8 yv:data-[size=default]:p-4 yv:*:shrink-0 yv:font-sans yv:flex yv:flex-col yv:w-full yv:grow yv:p-4 yv:rounded-2xl yv:bg-card' + 'yv:data-[size=lg]:p-8 yv:data-[size=default]:p-4 yv:*:shrink-0 yv:font-sans yv:flex yv:flex-col yv:w-full yv:grow yv:p-4 yv:rounded-2xl yv:bg-card yv:box-border' } - style={{ boxSizing: 'border-box' }} > -
+
{showSunIcon ? (
{ }); }); - it('should not set layout width on BibleTextView', async () => { - const { container } = render( - , - ); - - await waitFor(() => { - const wrapper = container.querySelector('[data-yv-sdk]'); - expect((wrapper as HTMLElement).style.width).toBe(''); - expect((wrapper as HTMLElement).style.maxWidth).toBe(''); - }); - }); - - it('should leave Verse.Html without a text width class', async () => { - const { container } = render(); - - await waitFor(() => { - const section = container.querySelector('section[data-slot="yv-bible-renderer"]'); - expect((section as HTMLElement).style.maxWidth).toBe(''); - }); - }); - it('should show spinner on initial load when passage is null', async () => { const { container } = render(