From a177546ceb4bcfce8e99395d61a7dd1793d4540a Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Thu, 17 Sep 2026 00:34:13 +0200 Subject: [PATCH 01/12] feat: add FilterIcon --- src/components/icons/FilterIcon.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/components/icons/FilterIcon.tsx diff --git a/src/components/icons/FilterIcon.tsx b/src/components/icons/FilterIcon.tsx new file mode 100644 index 0000000..5548a64 --- /dev/null +++ b/src/components/icons/FilterIcon.tsx @@ -0,0 +1,22 @@ +// Found as a part of Lucide: +// https://lucide.dev/icons/funnel + +function FilterIcon() { + return ( + + + + ); +} + +export { FilterIcon }; From 594dc5a90bc626084c80481374dac34d22812dc6 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Thu, 17 Sep 2026 00:34:49 +0200 Subject: [PATCH 02/12] feat: add additional filtering of books --- src/api/openLibrary.ts | 15 +++- src/api/types.ts | 10 +++ src/components/BookGrid/BookGrid.tsx | 28 +++++-- .../FilterBooksSection.module.css | 12 +++ .../FilterBooksSection/FilterBooksSection.tsx | 81 +++++++++++++++++++ src/components/SearchBar/SearchBar.module.css | 35 ++++++-- src/components/SearchBar/SearchBar.tsx | 39 ++++++--- 7 files changed, 196 insertions(+), 24 deletions(-) create mode 100644 src/components/FilterBooksSection/FilterBooksSection.module.css create mode 100644 src/components/FilterBooksSection/FilterBooksSection.tsx diff --git a/src/api/openLibrary.ts b/src/api/openLibrary.ts index 50a4395..767b971 100644 --- a/src/api/openLibrary.ts +++ b/src/api/openLibrary.ts @@ -3,6 +3,7 @@ import type { OpenLibrarySearchResponse, BookSearchParams, Book, + BookFilters, } from "./types"; const SEARCH_URL = "https://openlibrary.org/search.json"; @@ -21,8 +22,20 @@ const SEARCH_FIELDS: (keyof OpenLibraryDoc)[] = [ async function searchBooks( params: BookSearchParams, ): Promise { + // Create a string representation of the current filters + let filterString = ""; + + if (params.filters) { + for (const key in params.filters) { + if (key.length === 0) continue; + filterString += `${key}:${params.filters[key as keyof BookFilters]} `; + } + } + + const fullQuery = `${params.query} ${filterString}`.trim(); + const url = new URL(SEARCH_URL); - url.searchParams.set("q", params.query); + url.searchParams.set("q", fullQuery); url.searchParams.set("limit", String(SEARCH_LIMIT)); url.searchParams.set("fields", SEARCH_FIELDS.join(",")); diff --git a/src/api/types.ts b/src/api/types.ts index c3df2c5..9059695 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -14,6 +14,7 @@ export interface OpenLibraryDoc { export interface BookSearchParams { query: string; + filters?: BookFilters; // Add more search parameters here as needed. } @@ -24,3 +25,12 @@ export interface Book { publishedYear?: number; coverImage?: string; } + +export interface BookFilters { + author?: string; + subject?: string; + place?: string; + person?: string; + language?: string; + publisher?: string; +} diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx index 673d818..e72436c 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/src/components/BookGrid/BookGrid.tsx @@ -4,24 +4,42 @@ import { SearchBar } from "../SearchBar/SearchBar"; import styles from "./BookGrid.module.css"; import { useBooks } from "../../api/useBooks"; import { useDebounce } from "./useDebounce"; -import type { Book } from "../../api/types"; +import type { Book, BookFilters } from "../../api/types"; interface BookGridProps { onBookSelect: (book: Book) => void; } function BookGrid({ onBookSelect }: BookGridProps) { - const [searchQuery, setSearchQuery] = useState(""); + const [search, setSearch] = useState<{ + query: string; + filters: BookFilters; + }>({ + query: "", + filters: {}, + }); + + const debouncedSearch = useDebounce(search, 500); - const debouncedSearch = useDebounce(searchQuery, 500); - const query = debouncedSearch.trim() || "subject:fantasy"; + // If we have at least one filter, that is enough to perform a search. + // The query itself may be empty. + // Otherwise, use the default "subject:fantasy". + const query = + Object.keys(debouncedSearch.filters).length > 0 + ? debouncedSearch.query.trim() + : "subject:fantasy"; const { data, isPending, isError, error } = useBooks({ query, + filters: debouncedSearch.filters, }); const searchBar = ( - + setSearch({ ...search, query: value })} + onFilterChange={(value) => setSearch({ ...search, filters: value })} + /> ); if (isPending) { diff --git a/src/components/FilterBooksSection/FilterBooksSection.module.css b/src/components/FilterBooksSection/FilterBooksSection.module.css new file mode 100644 index 0000000..216433b --- /dev/null +++ b/src/components/FilterBooksSection/FilterBooksSection.module.css @@ -0,0 +1,12 @@ +.filter-books-section { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 2rem; +} + +.filter-options { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} diff --git a/src/components/FilterBooksSection/FilterBooksSection.tsx b/src/components/FilterBooksSection/FilterBooksSection.tsx new file mode 100644 index 0000000..5029fac --- /dev/null +++ b/src/components/FilterBooksSection/FilterBooksSection.tsx @@ -0,0 +1,81 @@ +import { useState } from "react"; +import styles from "./FilterBooksSection.module.css"; +import type { BookFilters } from "../../api/types"; + +const FILTER_INPUTS: { + name: keyof BookFilters; + placeholder: string; + ariaLabel: string; +}[] = [ + { + name: "author", + placeholder: "Search book authors...", + ariaLabel: "Search for book author", + }, + { + name: "subject", + placeholder: "Search book subjects...", + ariaLabel: "Search for book subject", + }, + { + name: "place", + placeholder: "Search places... (e.g., New York, London)", + ariaLabel: "Search for place", + }, + { + name: "person", + placeholder: "Search for people...", + ariaLabel: "Search for person", + }, + { + name: "language", + placeholder: "Search for languages... (e.g., English, French)", + ariaLabel: "Search for language", + }, + { + name: "publisher", + placeholder: "Search for publishers...", + ariaLabel: "Search for publisher", + }, +]; + +function FilterBooksSection({ + onFilterChange, +}: { + onFilterChange?: (filter: BookFilters) => void; +}) { + const [filters, setFilters] = useState({}); + + function handleFilterChange(field: keyof BookFilters, value: string) { + const newFilters = { ...filters, [field]: value }; + + if (value === "") { + delete newFilters[field]; + } + + setFilters(newFilters); + onFilterChange?.(newFilters); + } + + return ( +
+

Filter Books

+
+ {FILTER_INPUTS.map((input) => ( + handleFilterChange(input.name, e.target.value)} + /> + ))} +
+
+ ); +} + +export { FilterBooksSection }; diff --git a/src/components/SearchBar/SearchBar.module.css b/src/components/SearchBar/SearchBar.module.css index ea27572..d97873c 100644 --- a/src/components/SearchBar/SearchBar.module.css +++ b/src/components/SearchBar/SearchBar.module.css @@ -1,10 +1,22 @@ -.search-bar { - display: block; +.search-bar-container { + display: flex; + justify-content: center; + align-items: center; width: 100%; - max-width: 500px; - padding: 10px 16px; + gap: 2rem; margin: 80px auto 0 auto; + @media only screen and (max-width: 600px) { + width: calc(100% - 80px); /* Adjust width to account for the margins*/ + margin: 40px 40px 0 40px; + max-width: none; + } +} + +.search-bar { + width: 500px; + padding: 10px 16px; + border: 1px solid var(--border-color); border-radius: var(--border-radius); font-size: 1rem; @@ -13,10 +25,17 @@ &:hover { border-color: var(--theme-color); } +} - @media only screen and (max-width: 600px) { - width: calc(100% - 80px); /* Adjust width to account for the margins*/ - margin: 40px 40px 0 40px; - max-width: none; +.filter-button { + padding: 8px 16px; + color: var(--text-color); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + font-size: 1rem; + cursor: pointer; + + &:hover { + border-color: var(--theme-color); } } diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 549df0b..ef5d3d7 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -1,21 +1,40 @@ +import { useState } from "react"; import styles from "./SearchBar.module.css"; +import { FilterIcon } from "../icons/FilterIcon"; +import { FilterBooksSection } from "../FilterBooksSection/FilterBooksSection"; +import type { BookFilters } from "../../api/types"; interface SearchBarProps { query: string; onQueryChange: (newQuery: string) => void; + onFilterChange: (filter: BookFilters) => void; } -function SearchBar({ query, onQueryChange }: SearchBarProps) { +function SearchBar({ query, onQueryChange, onFilterChange }: SearchBarProps) { + const [filtersOpen, setFiltersOpen] = useState(false); + return ( - onQueryChange(e.target.value)} - /> + <> +
+ onQueryChange(e.target.value)} + /> + +
+ {filtersOpen && } + ); } From 02d8397ecb92cb3832466e9b07527ae7a5eaf05b Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Thu, 17 Sep 2026 17:18:16 +0200 Subject: [PATCH 03/12] feat: add labels and CSS styling to book filter inputs --- .../FilterBooksSection.module.css | 34 ++++++++++++++++--- .../FilterBooksSection/FilterBooksSection.tsx | 28 ++++++++------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/components/FilterBooksSection/FilterBooksSection.module.css b/src/components/FilterBooksSection/FilterBooksSection.module.css index 216433b..440f841 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.module.css +++ b/src/components/FilterBooksSection/FilterBooksSection.module.css @@ -5,8 +5,34 @@ margin-bottom: 2rem; } -.filter-options { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 1rem; +.filter-books-section h2 { + margin: 1rem 0; +} + +.filter-inputs-list { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + + @media only screen and (max-width: 900px) { + grid-template-columns: 1fr; + } +} + +.filter-input-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.filter-input { + padding: 0.5rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + border-radius: 4px; + font-size: 1rem; + + &:hover { + border-color: var(--theme-color); + } } diff --git a/src/components/FilterBooksSection/FilterBooksSection.tsx b/src/components/FilterBooksSection/FilterBooksSection.tsx index 5029fac..614d6f6 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.tsx +++ b/src/components/FilterBooksSection/FilterBooksSection.tsx @@ -60,20 +60,24 @@ function FilterBooksSection({ return (

Filter Books

-
+
    {FILTER_INPUTS.map((input) => ( - handleFilterChange(input.name, e.target.value)} - /> +
  • + + handleFilterChange(input.name, e.target.value)} + /> +
  • ))} -
+
); } From bf7529d553a30638bb49a413679cce85b659d9a4 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Thu, 17 Sep 2026 17:26:58 +0200 Subject: [PATCH 04/12] fix: allow searches with only filters or only query string --- src/components/BookGrid/BookGrid.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx index e594bff..156186a 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/src/components/BookGrid/BookGrid.tsx @@ -25,12 +25,13 @@ function BookGrid({ page, onBookSelect, onPageChange }: BookGridProps) { const debouncedSearch = useDebounce(search, 500); - // If we have at least one filter, that is enough to perform a search. - // The query itself may be empty. - // Otherwise, use the default "subject:fantasy". + // We can perform a search if we have either a string query, + // or at least one filter. + // If we have neither, use the default "subject:fantasy". + const hasFilters = Object.keys(debouncedSearch.filters).length > 0; const query = - Object.keys(debouncedSearch.filters).length > 0 - ? debouncedSearch.query.trim() + hasFilters || debouncedSearch.query.length > 0 + ? debouncedSearch.query : "subject:fantasy"; const { data, isPending, isError, error } = useBooks({ From 61466395d6d2c05f3dece1a3b880cc7f07efe44c Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Thu, 17 Sep 2026 18:08:08 +0200 Subject: [PATCH 05/12] feat: add language search --- src/api/languageCodes.ts | 61 +++++++++++++++++++ src/components/BookGrid/BookGrid.tsx | 9 +-- .../FilterBooksSection.module.css | 1 + .../FilterBooksSection/FilterBooksSection.tsx | 25 ++++++-- 4 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 src/api/languageCodes.ts diff --git a/src/api/languageCodes.ts b/src/api/languageCodes.ts new file mode 100644 index 0000000..2a4657e --- /dev/null +++ b/src/api/languageCodes.ts @@ -0,0 +1,61 @@ +// Original ISO 639-1 language codes fetched from: +// https://gist.github.com/jrnk/8eb57b065ea0b098d571 +// This is a smaller selection of languages and the codes are adjusted +// to the first three letters of the language name, as used by Open Library. +const languageCodes = [ + { code: "afr", name: "Afrikaans" }, + { code: "ara", name: "Arabic" }, + { code: "aze", name: "Azerbaijani" }, + { code: "bel", name: "Belarusian" }, + { code: "bul", name: "Bulgarian" }, + { code: "tib", name: "Tibetan" }, + { code: "bos", name: "Bosnian" }, + { code: "cze", name: "Czech" }, + { code: "wel", name: "Welsh" }, + { code: "dan", name: "Danish" }, + { code: "ger", name: "German" }, + { code: "gre", name: "Greek, Modern (1453-)" }, + { code: "eng", name: "English" }, + { code: "esp", name: "Esperanto" }, + { code: "spa", name: "Spanish; Castilian" }, + { code: "est", name: "Estonian" }, + { code: "per", name: "Persian" }, + { code: "fin", name: "Finnish" }, + { code: "fre", name: "French" }, + { code: "iri", name: "Irish" }, + { code: "hin", name: "Hindi" }, + { code: "cro", name: "Croatian" }, + { code: "hun", name: "Hungarian" }, + { code: "arm", name: "Armenian" }, + { code: "ind", name: "Indonesian" }, + { code: "ice", name: "Icelandic" }, + { code: "ita", name: "Italian" }, + { code: "jpn", name: "Japanese" }, + { code: "geo", name: "Georgian" }, + { code: "kor", name: "Korean" }, + { code: "kur", name: "Kurdish" }, + { code: "lat", name: "Latin" }, + { code: "lit", name: "Lithuanian" }, + { code: "lat", name: "Latvian" }, + { code: "mac", name: "Macedonian" }, + { code: "mal", name: "Malayalam" }, + { code: "mon", name: "Mongolian" }, + { code: "mal", name: "Maltese" }, + { code: "bur", name: "Burmese" }, + { code: "dut", name: "Dutch; Flemish" }, + { code: "nor", name: "Norwegian" }, + { code: "pan", name: "Panjabi; Punjabi" }, + { code: "pol", name: "Polish" }, + { code: "por", name: "Portuguese" }, + { code: "rom", name: "Romanian; Moldavian; Moldovan" }, + { code: "rus", name: "Russian" }, + { code: "alb", name: "Albanian" }, + { code: "ser", name: "Serbian" }, + { code: "swe", name: "Swedish" }, + { code: "tur", name: "Turkish" }, + { code: "ukr", name: "Ukrainian" }, + { code: "vie", name: "Vietnamese" }, + { code: "chi", name: "Chinese" }, +] as const; + +export { languageCodes }; diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx index 156186a..3d65972 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/src/components/BookGrid/BookGrid.tsx @@ -4,7 +4,7 @@ import { SearchBar } from "../SearchBar/SearchBar"; import styles from "./BookGrid.module.css"; import { useBooks } from "../../api/useBooks"; import { useDebounce } from "./useDebounce"; -import type { Book, BookFilters } from "../../api/types"; +import type { Book, BookSearchParams } from "../../api/types"; import { Pagination } from "../Pagination/Pagination"; import { SEARCH_LIMIT } from "../../api/openLibrary"; @@ -15,10 +15,7 @@ interface BookGridProps { } function BookGrid({ page, onBookSelect, onPageChange }: BookGridProps) { - const [search, setSearch] = useState<{ - query: string; - filters: BookFilters; - }>({ + const [search, setSearch] = useState({ query: "", filters: {}, }); @@ -28,7 +25,7 @@ function BookGrid({ page, onBookSelect, onPageChange }: BookGridProps) { // We can perform a search if we have either a string query, // or at least one filter. // If we have neither, use the default "subject:fantasy". - const hasFilters = Object.keys(debouncedSearch.filters).length > 0; + const hasFilters = Object.keys(debouncedSearch.filters ?? {}).length > 0; const query = hasFilters || debouncedSearch.query.length > 0 ? debouncedSearch.query diff --git a/src/components/FilterBooksSection/FilterBooksSection.module.css b/src/components/FilterBooksSection/FilterBooksSection.module.css index 440f841..2ef8144 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.module.css +++ b/src/components/FilterBooksSection/FilterBooksSection.module.css @@ -27,6 +27,7 @@ .filter-input { padding: 0.5rem; + background-color: white; border: 1px solid var(--border-color); border-radius: var(--border-radius); border-radius: 4px; diff --git a/src/components/FilterBooksSection/FilterBooksSection.tsx b/src/components/FilterBooksSection/FilterBooksSection.tsx index 614d6f6..9ea8323 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.tsx +++ b/src/components/FilterBooksSection/FilterBooksSection.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import styles from "./FilterBooksSection.module.css"; import type { BookFilters } from "../../api/types"; +import { languageCodes } from "../../api/languageCodes"; const FILTER_INPUTS: { name: keyof BookFilters; @@ -27,11 +28,6 @@ const FILTER_INPUTS: { placeholder: "Search for people...", ariaLabel: "Search for person", }, - { - name: "language", - placeholder: "Search for languages... (e.g., English, French)", - ariaLabel: "Search for language", - }, { name: "publisher", placeholder: "Search for publishers...", @@ -77,6 +73,25 @@ function FilterBooksSection({ /> ))} +
  • + + +
  • ); From 1845cdf413c38eea2b39a5e336630a523369255c Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Thu, 17 Sep 2026 18:09:29 +0200 Subject: [PATCH 06/12] feat: explain book language filter --- src/components/FilterBooksSection/FilterBooksSection.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/FilterBooksSection/FilterBooksSection.tsx b/src/components/FilterBooksSection/FilterBooksSection.tsx index 9ea8323..cba2ae9 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.tsx +++ b/src/components/FilterBooksSection/FilterBooksSection.tsx @@ -75,7 +75,9 @@ function FilterBooksSection({ ))}
  • Date: Fri, 18 Sep 2026 00:41:06 +0200 Subject: [PATCH 09/12] feat: use sessionStorage to save filters --- src/components/FilterBooksSection/FilterBooksSection.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/FilterBooksSection/FilterBooksSection.tsx b/src/components/FilterBooksSection/FilterBooksSection.tsx index 277e214..0318ba8 100644 --- a/src/components/FilterBooksSection/FilterBooksSection.tsx +++ b/src/components/FilterBooksSection/FilterBooksSection.tsx @@ -41,7 +41,10 @@ function FilterBooksSection({ }: { onFilterChange?: (filter: BookFilters) => void; }) { - const [filters, setFilters] = useState({}); + const rawFilters = sessionStorage.getItem("filters"); + const initFilters = JSON.parse(rawFilters ?? "{}") as BookFilters; + + const [filters, setFilters] = useState(initFilters); function handleFilterChange(field: keyof BookFilters, value: string) { const newFilters = { ...filters, [field]: value }; @@ -51,6 +54,7 @@ function FilterBooksSection({ } setFilters(newFilters); + sessionStorage.setItem("filters", JSON.stringify(newFilters)); onFilterChange?.(newFilters); } From 3af30a02b6d8be087cad146cc67bb91276fd9fdd Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Fri, 18 Sep 2026 17:58:00 +0200 Subject: [PATCH 10/12] refactor: move page search param to search state --- src/App.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0a2f721..b258417 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,6 @@ import type { Book, BookFilters, BookSearchParams } from "./api/types.ts"; function App() { const [selectedBook, setSelectedBook] = useState(null); - const [page, setPage] = useState(1); const rawFilters = sessionStorage.getItem("filters"); const initFilters = JSON.parse(rawFilters ?? "{}") as BookFilters; @@ -34,7 +33,7 @@ function App() { const booksQuery = useBooks({ query, - page, + page: search.page, filters: debouncedSearch.filters, }); @@ -54,9 +53,9 @@ function App() { booksQuery={booksQuery} search={search} onSearchChange={setSearch} - page={page} + page={search.page ?? 1} onBookSelect={setSelectedBook} - onPageChange={setPage} + onPageChange={(page) => setSearch({ ...search, page })} /> )} From f182ff15c13df9d03afc7b716282047919f37322 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Fri, 18 Sep 2026 18:14:21 +0200 Subject: [PATCH 11/12] feat: add badge showing number of filters active --- src/components/BookGrid/BookGrid.tsx | 1 + src/components/SearchBar/SearchBar.module.css | 16 ++++++++++++++++ src/components/SearchBar/SearchBar.tsx | 14 +++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx index 04be833..f067c71 100644 --- a/src/components/BookGrid/BookGrid.tsx +++ b/src/components/BookGrid/BookGrid.tsx @@ -29,6 +29,7 @@ function BookGrid({ const searchBar = ( onSearchChange({ ...search, query: value })} onFilterChange={(value) => onSearchChange({ ...search, filters: value })} /> diff --git a/src/components/SearchBar/SearchBar.module.css b/src/components/SearchBar/SearchBar.module.css index d97873c..f3c1cd8 100644 --- a/src/components/SearchBar/SearchBar.module.css +++ b/src/components/SearchBar/SearchBar.module.css @@ -28,6 +28,7 @@ } .filter-button { + position: relative; padding: 8px 16px; color: var(--text-color); border: 1px solid var(--border-color); @@ -39,3 +40,18 @@ border-color: var(--theme-color); } } + +.filter-button span { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + bottom: 0; + right: 0; + transform: translate(50%, 50%); + border-radius: 100%; + background-color: var(--theme-color); + color: white; + min-width: 2rem; + min-height: 2rem; +} diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index ef5d3d7..b0a1aac 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -6,12 +6,21 @@ import type { BookFilters } from "../../api/types"; interface SearchBarProps { query: string; + filters?: BookFilters; onQueryChange: (newQuery: string) => void; onFilterChange: (filter: BookFilters) => void; } -function SearchBar({ query, onQueryChange, onFilterChange }: SearchBarProps) { +function SearchBar({ + query, + filters, + onQueryChange, + onFilterChange, +}: SearchBarProps) { const [filtersOpen, setFiltersOpen] = useState(false); + const filtersCount = filters ? Object.keys(filters).length : 0; + + const filtersDescription = `${filtersCount} ${filtersCount === 1 ? "filter" : "filters"} active`; return ( <> @@ -31,6 +40,9 @@ function SearchBar({ query, onQueryChange, onFilterChange }: SearchBarProps) { aria-label="Filter books" > + {filtersCount > 0 && ( + {filtersCount} + )} {filtersOpen && } From 4e0f735f4fa902e2178e5d24b01170453c0d5dbf Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Fri, 18 Sep 2026 20:47:36 +0200 Subject: [PATCH 12/12] fix: height adjustment of search bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Henrik Strøm-Andersen --- src/components/SearchBar/SearchBar.module.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/SearchBar/SearchBar.module.css b/src/components/SearchBar/SearchBar.module.css index f3c1cd8..6d06fd0 100644 --- a/src/components/SearchBar/SearchBar.module.css +++ b/src/components/SearchBar/SearchBar.module.css @@ -3,7 +3,7 @@ justify-content: center; align-items: center; width: 100%; - gap: 2rem; + gap: 1rem; margin: 80px auto 0 auto; @media only screen and (max-width: 600px) { @@ -15,7 +15,8 @@ .search-bar { width: 500px; - padding: 10px 16px; + padding: 0 16px; + height: 48px; border: 1px solid var(--border-color); border-radius: var(--border-radius); @@ -29,7 +30,9 @@ .filter-button { position: relative; - padding: 8px 16px; + padding: 0 16px; + height: 48px; + background-color: white; color: var(--text-color); border: 1px solid var(--border-color); border-radius: var(--border-radius);