diff --git a/src/App.css b/src/App.css index 7188ed3..12675f8 100644 --- a/src/App.css +++ b/src/App.css @@ -29,6 +29,20 @@ padding: 1.5rem; } +.library-controls { + display: grid; + gap: 1rem; + max-width: 42rem; + margin: 0 auto 1.5rem; +} + +.library-controls .subject-filter, +.library-controls .sort-select { + width: 100%; + max-width: none; + margin: 0; +} + .book-viewer { max-width: 32rem; margin: 0 auto; @@ -40,3 +54,21 @@ color: #616e7c; font-size: 0.875rem; } + +@media (min-width: 42rem) { + .library-controls { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 32rem) { + .app-header, + .app-main { + padding-right: 1rem; + padding-left: 1rem; + } + + .app-header h1 { + font-size: 1.5rem; + } +} diff --git a/src/App.tsx b/src/App.tsx index 25a4c54..ed9215b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,8 +41,10 @@ function App() {

Browse reading material from OpenLibrary, one book at a time.

- - +
+ + +
{isLoading &&

Loading books…

} {isError &&

Could not load books. Please try refreshing the page.

} {!isLoading && !isError && books.length === 0 && ( diff --git a/src/__snapshots__/App.test.tsx.snap b/src/__snapshots__/App.test.tsx.snap index 69cfc6c..99d18d7 100644 --- a/src/__snapshots__/App.test.tsx.snap +++ b/src/__snapshots__/App.test.tsx.snap @@ -18,76 +18,80 @@ exports[`App > matches the snapshot 1`] = `
- - + + + + + + + +
matches the snapshot 1`] = ` 1968

  • diff --git a/src/components/BookCard.css b/src/components/BookCard.css index a5aad97..3ab6294 100644 --- a/src/components/BookCard.css +++ b/src/components/BookCard.css @@ -1,6 +1,6 @@ .book-card { display: grid; - grid-template-columns: minmax(8rem, 12rem) 1fr; + grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr); gap: 1.5rem; overflow: hidden; border: 1px solid #d5dde5; @@ -11,6 +11,7 @@ .book-card-cover { display: grid; + align-self: stretch; min-height: 12rem; place-items: center; background-color: #edf2f7; @@ -35,6 +36,7 @@ .book-card-content h2 { margin: 0; color: #243b53; + overflow-wrap: anywhere; } .book-card-content p { @@ -56,11 +58,20 @@ } .book-card-subjects li { + min-width: 0; + max-width: 100%; border-radius: 999px; background-color: #d9e2ec; color: #243b53; padding: 0.25rem 0.625rem; font-size: 0.875rem; + overflow-wrap: anywhere; +} + +.book-card-subjects .book-card-subjects-more { + background-color: #edf2f7; + color: #52606d; + font-weight: 600; } @media (max-width: 32rem) { @@ -69,10 +80,15 @@ } .book-card-cover { - min-height: 16rem; + min-height: 0; + aspect-ratio: 4 / 3; } .book-card-content { + position: relative; + z-index: 1; padding: 0 1.25rem 1.25rem; + border-top: 1px solid #d5dde5; + background-color: #ffffff; } } diff --git a/src/components/BookCard.test.tsx b/src/components/BookCard.test.tsx index 4e74de9..e7d853f 100644 --- a/src/components/BookCard.test.tsx +++ b/src/components/BookCard.test.tsx @@ -28,7 +28,9 @@ describe("BookCard", () => { expect(screen.getByRole("heading", { name: fullBook.title })).toBeInTheDocument(); expect(screen.getByText("Ursula K. Le Guin, Another Author")).toBeInTheDocument(); expect(screen.getByRole("img", { name: "Cover of A Wizard of Earthsea" })).toBeInTheDocument(); - expect(screen.getByRole("list", { name: "Subjects" })).toHaveTextContent("Fantasy"); + expect(screen.getByRole("list", { name: "Subjects, showing 2 of 2" })).toHaveTextContent( + "Fantasy", + ); expect(container).toMatchSnapshot(); }); @@ -41,4 +43,17 @@ describe("BookCard", () => { expect(screen.queryByRole("img")).not.toBeInTheDocument(); expect(container).toMatchSnapshot(); }); + + it("limits long subject lists and shows how many subjects remain", () => { + const bookWithManySubjects: Book = { + ...fullBook, + subjects: ["Fantasy", "Magic", "Adventure", "Dragons", "Wizards", "Earthsea"], + }; + + render(); + + expect(screen.getByRole("list", { name: "Subjects, showing 5 of 6" })).toBeInTheDocument(); + expect(screen.getByText("+1 more")).toBeInTheDocument(); + expect(screen.queryByText("Earthsea")).not.toBeInTheDocument(); + }); }); diff --git a/src/components/BookCard.tsx b/src/components/BookCard.tsx index 72d95fd..7518ce4 100644 --- a/src/components/BookCard.tsx +++ b/src/components/BookCard.tsx @@ -3,6 +3,8 @@ import type { Book } from "../types"; import { FavoriteButton } from "./FavoriteButton"; import "./BookCard.css"; +const MAX_VISIBLE_SUBJECTS = 5; + interface BookCardProps { book: Book; isFavorite?: boolean; @@ -12,6 +14,8 @@ interface BookCardProps { export function BookCard({ book, isFavorite = false, onFavoriteToggle }: BookCardProps) { const imageUrl = coverUrl(book.coverId); const authors = book.authors.length > 0 ? book.authors.join(", ") : "Unknown author"; + const visibleSubjects = book.subjects.slice(0, MAX_VISIBLE_SUBJECTS); + const remainingSubjectCount = book.subjects.length - visibleSubjects.length; return (
    @@ -27,10 +31,16 @@ export function BookCard({ book, isFavorite = false, onFavoriteToggle }: BookCar

    {authors}

    {book.firstPublishYear !== null &&

    First published: {book.firstPublishYear}

    } {book.subjects.length > 0 && ( -
      - {book.subjects.map((subject) => ( +
        + {visibleSubjects.map((subject) => (
      • {subject}
      • ))} + {remainingSubjectCount > 0 && ( +
      • +{remainingSubjectCount} more
      • + )}
      )} {onFavoriteToggle && ( diff --git a/src/components/FavoriteButton.css b/src/components/FavoriteButton.css index 5007bc2..e443cfe 100644 --- a/src/components/FavoriteButton.css +++ b/src/components/FavoriteButton.css @@ -1,5 +1,6 @@ .favorite-button { align-self: flex-start; + margin-top: 1rem; padding: 0.5rem 0.75rem; border: 1px solid #cbd2d9; border-radius: 0.375rem; diff --git a/src/components/__snapshots__/BookCard.test.tsx.snap b/src/components/__snapshots__/BookCard.test.tsx.snap index 3d54a76..f60ba5c 100644 --- a/src/components/__snapshots__/BookCard.test.tsx.snap +++ b/src/components/__snapshots__/BookCard.test.tsx.snap @@ -29,7 +29,7 @@ exports[`BookCard > renders a complete book 1`] = ` 1968