From 4e01e13ee7c7c81f2b63c1a3099dc54a7c59d0c0 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Thu, 28 May 2026 02:25:18 +0800 Subject: [PATCH] fix(library): make bookitem-main shrink to match cover in fit mode (#4331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(library): make bookitem-main shrink to match cover in fit mode Closes #4234. In fit mode the bookitem-main kept its 28/41 aspect regardless of the cover image's natural aspect, leaving extra padding beside (portrait covers) or above (landscape covers) the cover. The select-mode overlay and icons drifted away from the cover edge. BookCover now reports the loaded image's natural aspect ratio. BookItem overrides the bookitem-main's aspect-ratio with the cover's aspect so the box hugs the cover exactly, and proportionally shrinks book-item width for portrait covers so the info row icons align with the cover's right edge. Also wrap the TTS "Back to TTS Location" pill with whitespace-nowrap so long translations (e.g. German "Zurück zur TTS-Position") expand the button width instead of overflowing the fixed height. Co-Authored-By: Claude Opus 4.7 (1M context) * fix(library): scope cover shrink to bookitem-main, leave info row at cell width Per review, the width shrink should only apply to the cover row so the title and info icons keep their original cell-wide layout. Move the width style from .book-item to .bookitem-main alongside its aspectRatio override. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .../__tests__/components/BookCover.test.tsx | 17 ++++++++++++++- .../src/app/library/components/BookItem.tsx | 21 ++++++++++++++++++- .../app/reader/components/tts/TTSControl.tsx | 2 +- apps/readest-app/src/components/BookCover.tsx | 8 ++++++- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/readest-app/src/__tests__/components/BookCover.test.tsx b/apps/readest-app/src/__tests__/components/BookCover.test.tsx index 572c3a9b..108b727a 100644 --- a/apps/readest-app/src/__tests__/components/BookCover.test.tsx +++ b/apps/readest-app/src/__tests__/components/BookCover.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; -import { render, cleanup } from '@testing-library/react'; +import { render, cleanup, fireEvent } from '@testing-library/react'; import BookCover from '@/components/BookCover'; import { Book } from '@/types/book'; @@ -39,6 +39,21 @@ describe('BookCover', () => { expect(img?.getAttribute('loading')).toBe('lazy'); }); + it('reports natural aspect ratio via onAspectRatioChange when fit-mode image loads', () => { + const onAspectRatioChange = vi.fn(); + const { container } = render( + , + ); + const img = container.querySelector('img.cover-image') as HTMLImageElement; + expect(img).toBeTruthy(); + + Object.defineProperty(img, 'naturalWidth', { value: 600, configurable: true }); + Object.defineProperty(img, 'naturalHeight', { value: 900, configurable: true }); + fireEvent.load(img); + + expect(onAspectRatioChange).toHaveBeenCalledWith(600 / 900); + }); + it('falls back to metadata.author on the fallback cover when book.author is empty', () => { const book = makeBook({ author: '', diff --git a/apps/readest-app/src/app/library/components/BookItem.tsx b/apps/readest-app/src/app/library/components/BookItem.tsx index a3d6221d..6159b180 100644 --- a/apps/readest-app/src/app/library/components/BookItem.tsx +++ b/apps/readest-app/src/app/library/components/BookItem.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import { useEffect, useState } from 'react'; import { MdCheckCircle, MdCheckCircleOutline } from 'react-icons/md'; import { LiaCloudUploadAltSolid, @@ -49,6 +50,21 @@ const BookItem: React.FC = ({ const { settings } = useSettingsStore(); const iconSize15 = useResponsiveSize(15); + const [coverAspect, setCoverAspect] = useState(null); + useEffect(() => { + setCoverAspect(null); + }, [book.hash, book.metadata?.coverImageUrl, book.coverImageUrl]); + + const CELL_ASPECT_RATIO = 28 / 41; + const fitCoverInGrid = mode === 'grid' && coverFit === 'fit' && coverAspect !== null; + const shouldShrinkWidth = fitCoverInGrid && coverAspect! < CELL_ASPECT_RATIO; + const bookitemMainStyle = fitCoverInGrid + ? { + aspectRatio: coverAspect!, + ...(shouldShrinkWidth ? { width: `${(coverAspect! / CELL_ASPECT_RATIO) * 100}%` } : {}), + } + : undefined; + return (
= ({ >
= ({ coverFit={coverFit} showSpine={false} imageClassName='rounded shadow-md' + onAspectRatioChange={setCoverAspect} /> {bookSelected && (
diff --git a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx index 11a0b247..56174c5e 100644 --- a/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx +++ b/apps/readest-app/src/app/reader/components/tts/TTSControl.tsx @@ -175,7 +175,7 @@ const TTSControl: React.FC = ({ bookKey, gridInsets }) => {