From abdf060478b40ba8899cf8460878f66bd3227239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Thu, 1 Oct 2026 14:24:24 +0200 Subject: [PATCH 1/7] fix: changed index.css path in main.tsx Apparently this got left out in an earlier session. Now references the correct index.css. --- client/src/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main.tsx b/client/src/main.tsx index bef5202..d68fac9 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,6 +1,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import './index.css' +import './styles/index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( From e4c5abca9f6698522a4ea37c3dfc31c925af7902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Thu, 1 Oct 2026 14:59:10 +0200 Subject: [PATCH 2/7] feat: add shared component Button Includes two variants (default: primary): - Primary = transparent background with dark purple border and text. - Secondary = borderless rose background with white text. Two types (default: button, to avoid accidental form submits): - button - submit Two flexible sizes (default: narrow): - narrow: less horizontal padding - wide: more horizontal padding Example: "Load more books" Primary, type=button, wide A wider, transparent button with dark purple outline. Example: "Save note" Secondary, type=submit, narrow A smaller, rose colored button with white text. Wrap text field and button in
to use submit. --- .../src/components/Button/Button.module.css | 38 +++++++++++++++++++ client/src/components/Button/Button.tsx | 30 +++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 client/src/components/Button/Button.module.css create mode 100644 client/src/components/Button/Button.tsx diff --git a/client/src/components/Button/Button.module.css b/client/src/components/Button/Button.module.css new file mode 100644 index 0000000..0fad132 --- /dev/null +++ b/client/src/components/Button/Button.module.css @@ -0,0 +1,38 @@ +.button { + height: 44px; + border-radius: 12px; + border: 1.5px solid transparent; + font-size: 15px; + font-weight: 700; + cursor: pointer; +} + +.primary { + border-color: var(--color-focus); + background: transparent; + color: var(--color-focus); +} + +.secondary { + border-color: var(--color-accent); + background: var(--color-accent); + color: var(--color-on-accent); +} + +.narrow { + padding: 0 20px; +} + +.wide { + padding: 0 40px; +} + +.button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.button:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 3px; +} \ No newline at end of file diff --git a/client/src/components/Button/Button.tsx b/client/src/components/Button/Button.tsx new file mode 100644 index 0000000..711a58f --- /dev/null +++ b/client/src/components/Button/Button.tsx @@ -0,0 +1,30 @@ +import styles from './Button.module.css'; + +type ButtonProps = { + children: React.ReactNode; + variant?: "primary" | "secondary"; + size?: "narrow" | "wide"; + type?: "button" | "submit"; + onClick?: () => void; + disabled?: boolean; +}; + +export function Button({ + children, + variant = "primary", + size = "narrow", + type = "button", + onClick, + disabled = false, +}: ButtonProps) { + return ( + + ); +} From 10e62b33002c1c61f383222e7a358fdfb048fc42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Thu, 1 Oct 2026 22:27:07 +0200 Subject: [PATCH 3/7] feat: add icons and clean up tokens.css --- .../components/SearchField/SearchField.tsx | 15 ++------- client/src/icons/BookmarkIcon.tsx | 9 ++++++ client/src/icons/BrowseIcon.tsx | 21 ++++++++++++ client/src/icons/LogoIcon.tsx | 32 +++++++++++++++++++ client/src/icons/SearchIcons.tsx | 21 ++++++++++++ client/src/icons/ShelfIcon.tsx | 20 ++++++++++++ client/src/styles/tokens.css | 14 ++++---- 7 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 client/src/icons/BookmarkIcon.tsx create mode 100644 client/src/icons/BrowseIcon.tsx create mode 100644 client/src/icons/LogoIcon.tsx create mode 100644 client/src/icons/SearchIcons.tsx create mode 100644 client/src/icons/ShelfIcon.tsx diff --git a/client/src/components/SearchField/SearchField.tsx b/client/src/components/SearchField/SearchField.tsx index 8ae089e..e1f0f96 100644 --- a/client/src/components/SearchField/SearchField.tsx +++ b/client/src/components/SearchField/SearchField.tsx @@ -1,5 +1,6 @@ import { useId, useState } from 'react'; import styles from './SearchField.module.css'; +import { SearchIcon } from '../../icons/SearchIcons'; /* ---------- Props ---------- */ type Props = { @@ -44,19 +45,7 @@ export function SearchField({ value, onSearch }: Props) { {/* Icon-only button, so it needs an aria-label. */} ); diff --git a/client/src/icons/BookmarkIcon.tsx b/client/src/icons/BookmarkIcon.tsx new file mode 100644 index 0000000..9b0848e --- /dev/null +++ b/client/src/icons/BookmarkIcon.tsx @@ -0,0 +1,9 @@ +export function BookmarkIcon() { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/BrowseIcon.tsx b/client/src/icons/BrowseIcon.tsx new file mode 100644 index 0000000..8d6c212 --- /dev/null +++ b/client/src/icons/BrowseIcon.tsx @@ -0,0 +1,21 @@ +/* ---------- Props ---------- */ +type Props = { size?: number }; + +/* ---------- BrowseIcon: open book (nav tab for Browse) ---------- */ +export function BrowseIcon({ size = 22 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/LogoIcon.tsx b/client/src/icons/LogoIcon.tsx new file mode 100644 index 0000000..be1e34e --- /dev/null +++ b/client/src/icons/LogoIcon.tsx @@ -0,0 +1,32 @@ +/* ---------- Props ---------- */ +type Props = { size?: number }; + +/* ---------- LogoIcon: open book with a gold sparkle (header logo) ---------- */ +export function LogoIcon({ size = 30 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/SearchIcons.tsx b/client/src/icons/SearchIcons.tsx new file mode 100644 index 0000000..9b4b7eb --- /dev/null +++ b/client/src/icons/SearchIcons.tsx @@ -0,0 +1,21 @@ +/* ---------- Props ---------- */ +type Props = { size?: number }; + +/* ---------- SearchIcon: magnifying glass (search button) ---------- */ +export function SearchIcon({ size = 18 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/ShelfIcon.tsx b/client/src/icons/ShelfIcon.tsx new file mode 100644 index 0000000..cb5a130 --- /dev/null +++ b/client/src/icons/ShelfIcon.tsx @@ -0,0 +1,20 @@ +/* ---------- Props ---------- */ +type Props = { size?: number }; + +/* ---------- ShelfIcon: bookmark (nav tab for My shelf) ---------- */ +export function ShelfIcon({ size = 22 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/styles/tokens.css b/client/src/styles/tokens.css index ed40073..c1a745e 100644 --- a/client/src/styles/tokens.css +++ b/client/src/styles/tokens.css @@ -7,7 +7,11 @@ --color-input-border: #8a6f7c; --color-header: #4a1f4f; --color-header-text: #fbeef3; + --color-header-tint: #f3b2cb; + --color-header-soft: #ffd8e6; --color-accent: #a3264f; + --color-accent-hover: #8a1f42; + --color-on-accent: #ffffff; --color-ribbon: #c2810f; --color-highlight: #eeb34c; --color-star: #c78a12; @@ -15,14 +19,8 @@ --color-chip-text: #7a1f45; --color-shelf: #7b5535; --color-shelf-edge: #9a7050; - --color-header-tint: #f3b2cb; - --color-header-soft: #ffd8e6; - - /* TODO:check contrast */ - --color-on-accent: #ffffff; /* text on accent buttons */ - --color-accent-hover: #8a1f42; /* darker accent for hover */ - --color-focus: #4a1f4f; /* focus ring on light areas; use --color-highlight inside the header */ - --color-danger: #b3261e; /* destructive actions, e.g. delete review */ + --color-focus: #4a1f4f; + --color-danger: #b3261e; /* Generated book cover tones (bg / text pairs) */ --cover-0-bg: #6a2a5e; From a934182f503ca7870a485f4f9e7d4d8729a0b261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Thu, 1 Oct 2026 22:37:17 +0200 Subject: [PATCH 4/7] Feat: add AppShell layout w/ header, search, nav, and footer - AppShell wraps all pages via Outlet; owns constant page width - Header: logo, SearchField (writes q to URL), main nav - Mobile-first CSS: nav is a fixed bottom tab bar, desktop media query at 48rem - Temporary placeholder pages in App.tsx for testing --- client/src/App.tsx | 29 ++- .../components/AppShell/AppShell.module.css | 199 ++++++++++++++++++ client/src/components/AppShell/AppShell.tsx | 87 ++++++++ 3 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 client/src/components/AppShell/AppShell.module.css create mode 100644 client/src/components/AppShell/AppShell.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 7c3ec04..9e704ea 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,28 @@ -function App() { - return

πŸ₯‚Project 2πŸ₯‚

; +import { BrowserRouter, Routes, Route } from "react-router"; +import AppShell from "./components/AppShell/AppShell"; + +/* ---------- Temporary placeholder pages (delete when the real ones exist) ---------- */ +function BrowsePlaceholder() { +return

Browse page

; +} +function ShelfPlaceholder() { + return

My shelf page

; +} +function DetailPlaceholder() { + return

Book detail page

; } -export default App; +/* ---------- Routes ---------- */ +export default function App() { + return ( + + + }> + } /> + } /> + } /> + + + + ); +} diff --git a/client/src/components/AppShell/AppShell.module.css b/client/src/components/AppShell/AppShell.module.css new file mode 100644 index 0000000..4228fa6 --- /dev/null +++ b/client/src/components/AppShell/AppShell.module.css @@ -0,0 +1,199 @@ +/* ---------- Shell: full-height column, so the footer sits at the bottom on short pages ---------- */ +.shell { + --nav-height: 3.75rem; /* used by the fixed bottom nav and the space reserved for it */ + + display: flex; + flex-direction: column; + min-height: 100vh; + min-height: 100dvh; /* mobile browsers: ignores the collapsing address bar */ + padding-bottom: calc(var(--nav-height) + var(--space-3)); /* room so the fixed nav doesn't cover the footer */ + background: var(--color-page); + color: var(--color-text); +} + +/* ---------- Inner: the constant page width, shared by header, main and footer ---------- */ +.inner { + width: 100%; + max-width: var(--content-width); + margin-inline: auto; + padding-inline: var(--space-4); +} + +/* ---------- Header: full-width plum bar ---------- */ +.header { + flex-shrink: 0; + padding-block: var(--space-4); + background: var(--color-header); + color: var(--color-header-text); +} + +/* Mobile: logo and search stacked (the nav is fixed to the bottom, so it's out of this flow) */ +.headerInner { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +/* ---------- Logo ---------- */ +.logo { + display: flex; + align-items: center; + align-self: flex-start; /* keeps the clickable area to the logo, not the full row */ + gap: var(--space-2); + min-height: var(--tap-target); + font-family: var(--font-heading); + font-size: var(--text-xl); + font-style: italic; + font-weight: 600; + color: var(--color-header-text); + text-decoration: none; +} + +/* ---------- Search wrapper: lets AppShell control the field's width ---------- */ +.search { + min-width: 0; /* allows the field to shrink inside a flex row */ +} + +/* ---------- Nav: mobile = tab bar pinned to the bottom of the screen ---------- */ +.nav { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 10; /* page content scrolls underneath */ + display: flex; + background: var(--color-card); + border-top: 1px solid var(--color-line); +} + +/* ---------- Nav link: mobile = icon above the label, equal-width tabs ---------- */ +.navLink { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-1); + min-height: var(--nav-height); + font-size: var(--text-sm); + font-weight: 700; + color: var(--color-text-soft); + text-decoration: none; + border-top: 3px solid transparent; /* reserves space, so the active marker doesn't shift things */ +} + +/* ---------- Active link: must come after .navLink, because the later rule wins ---------- */ +.active { + color: var(--color-header); + border-top-color: var(--color-accent); +} + +/* ---------- Divider: decorative gold double line under the header ---------- */ +.divider { + flex-shrink: 0; + height: 3px; + background: var(--color-header); + border-top: 1px solid var(--color-highlight); + border-bottom: 1px solid var(--color-highlight); +} + +/* ---------- Main: grows to fill the free space, pushing the footer down ---------- */ +.main { + flex: 1; + padding-block: var(--space-6); +} + +/* ---------- Footer ---------- */ +.footer { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: var(--space-2); +} + +/* Mobile: wordmark above the text */ +.footerInner { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.wordmark { + font-family: var(--font-heading); + font-size: var(--text-md); + font-style: italic; + font-weight: 600; + color: var(--color-header); +} + +/* ================= Desktop: from 768px and up ================= */ +@media (min-width: 48rem) { + .shell { + padding-bottom: var(--space-1); + } + + .inner { + padding-inline: var(--space-8); + } + + /* Header: one row, fixed height */ + .header { + padding-block: 0; + } + + .headerInner { + flex-direction: row; + align-items: center; + gap: var(--space-8); + min-height: 4.5rem; + } + + .logo { + align-self: center; + } + + .search { + flex: 1; + max-width: 32.5rem; + } + + /* Nav moves back into the header row, pushed to the right */ + .nav { + position: static; + margin-left: auto; + gap: var(--space-2); + background: none; + border-top: 0; + } + + .navLink { + flex: none; + flex-direction: row; + gap: var(--space-2); + min-height: var(--tap-target); + padding-inline: var(--space-3); + font-size: var(--text-md); + color: var(--color-header-text); + opacity: 0.8; /* inactive links are slightly dimmed */ + border-top: 0; + border-bottom: 2px solid transparent; + } + + .active { + color: var(--color-header-text); + opacity: 1; + border-bottom-color: var(--color-highlight); + } + + .main { + padding-block: calc(var(--space-6) * 2); + } + + /* Footer: wordmark left, text right */ + .footerInner { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} \ No newline at end of file diff --git a/client/src/components/AppShell/AppShell.tsx b/client/src/components/AppShell/AppShell.tsx new file mode 100644 index 0000000..b1046db --- /dev/null +++ b/client/src/components/AppShell/AppShell.tsx @@ -0,0 +1,87 @@ +// ===== Imports ===== +import { Outlet, Link, NavLink, useNavigate, useSearchParams } from "react-router-dom"; +import styles from './AppShell.module.css'; +import { SearchField } from "../SearchField/SearchField"; +import { LogoIcon } from "../../icons/LogoIcon"; +import { BrowseIcon } from "../../icons/BrowseIcon"; +import { ShelfIcon } from "../../icons/ShelfIcon"; +// TODO: Import SearchField from "../SearchField/SearchField" + +export default function AppShell() { + + // ===== Read the current search ===== + const [searchParam] = useSearchParams(); + const q = searchParam.get("q") ?? ""; + + const navigate = useNavigate(); + + function handleSearch(query: string) { + if (query === "") { + navigate("/"); + } else { + navigate("/?q=" + encodeURIComponent(query)); + } + } + + return ( + // ===== Shell wrapper ===== +
+ {/* ===== Header ===== */} +
+
+ {/* ===== Logo ===== */} + + + Bookish + + + {/* ===== Search field ===== */} +
+ +
+ + {/* ===== Main navigation ===== */} + +
+
+ + {/* ===== Header divider ===== */} + + ); +} \ No newline at end of file From cd26fdda571215c0cfff0eeb6ef22e30f0bbab99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Fri, 2 Oct 2026 09:03:24 +0200 Subject: [PATCH 5/7] doc: Update docs to reflect new design features + components Also delete unused Bookmark.tsx icon file Clean up forgotten TODO comment in AppShell.tsx --- client/src/components/AppShell/AppShell.tsx | 1 - client/src/icons/BookmarkIcon.tsx | 9 --- docs/decisions-and-contracts.md | 8 ++- docs/project-checklist.md | 2 +- docs/styles-and-components.md | 75 +++++++++++++++++++++ docs/styles_documentation.md | 33 --------- 6 files changed, 81 insertions(+), 47 deletions(-) delete mode 100644 client/src/icons/BookmarkIcon.tsx create mode 100644 docs/styles-and-components.md delete mode 100644 docs/styles_documentation.md diff --git a/client/src/components/AppShell/AppShell.tsx b/client/src/components/AppShell/AppShell.tsx index b1046db..ec76433 100644 --- a/client/src/components/AppShell/AppShell.tsx +++ b/client/src/components/AppShell/AppShell.tsx @@ -5,7 +5,6 @@ import { SearchField } from "../SearchField/SearchField"; import { LogoIcon } from "../../icons/LogoIcon"; import { BrowseIcon } from "../../icons/BrowseIcon"; import { ShelfIcon } from "../../icons/ShelfIcon"; -// TODO: Import SearchField from "../SearchField/SearchField" export default function AppShell() { diff --git a/client/src/icons/BookmarkIcon.tsx b/client/src/icons/BookmarkIcon.tsx deleted file mode 100644 index 9b0848e..0000000 --- a/client/src/icons/BookmarkIcon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export function BookmarkIcon() { - return ( - - ); -} \ No newline at end of file diff --git a/docs/decisions-and-contracts.md b/docs/decisions-and-contracts.md index d05477a..807f4a6 100644 --- a/docs/decisions-and-contracts.md +++ b/docs/decisions-and-contracts.md @@ -1,8 +1,9 @@ # Decisions and contracts -v1: created by Ilse, 29.09.2026 +[Mockup](bookish-mockup.html) (light mode) -Mockup: `docs/bookish-mockups.html/` (light mode) +Mockup is a pure HTML file - open in a browser to render visually. +**Note:** The mockup was created before we started building the app, so the real design might change during the project, and the mockup might not get updated when this happens. ## The contract between backend and frontend @@ -89,4 +90,5 @@ UserID: Client only knows its own ID. Server stores IDs along with shelf entries ## Changelog - v1 (29.09.2026): created by Ilse. -- v2 (date): `Book` type aligned with the backend model. CSS approach decided. Added decisions 12 and 13, the routes table, and more open questions. \ No newline at end of file +- v2 (01.10.2026): `Book` type aligned with the backend model. CSS approach decided. Added decisions 12 and 13, the routes table, and more open questions. +- v3 (02.10.2026): Added disclaimer about real design possibly diverging from initial design mockup. \ No newline at end of file diff --git a/docs/project-checklist.md b/docs/project-checklist.md index 3173737..bac14f8 100644 --- a/docs/project-checklist.md +++ b/docs/project-checklist.md @@ -1,6 +1,6 @@ # Project checklist: things to keep in mind -Drafted 2026-09-29 from the Project 2 description, the lectures and the Project 1 peer review. "Suggested owner" is a proposal for the group to change. Deadlines are on Canvas and are not in this file. +Drafted 2026-09-29 from the Project 2 description, the lectures and the Project 1 peer review. This document was created in the planning phase and might not get updated regularly. Roles: **A** Data, **B** API, **C** Browse + Shelf, **D** Design + Detail. See `roles.md`. diff --git a/docs/styles-and-components.md b/docs/styles-and-components.md new file mode 100644 index 0000000..f30ea2c --- /dev/null +++ b/docs/styles-and-components.md @@ -0,0 +1,75 @@ +# Rules and information regarding styles, design and components + +## Folder structure +``` +src/ +β”œβ”€β”€ styles/ tokens.css, reset.css, base.css, index.css +β”œβ”€β”€ icons/ Logo, Browse, Shelf, Search +β”œβ”€β”€ components/ +β”‚ β”œβ”€β”€ Button/ 3 variants used throughout app +β”‚ β”œβ”€β”€ BookCover/ image, fallback, color pick +β”‚ β”œβ”€β”€ BookCard/ BookCard + ribbon + starsdisplay +β”‚ β”œβ”€β”€ BookGrid/ render auto grid of BookCard, with shelf option +β”‚ β”œβ”€β”€ SearchField/ draft state, submit on Enter or button +β”‚ β”œβ”€β”€ FilterPanel/ genre, year, clear, mobile toggle +β”‚ β”œβ”€β”€ RatingInput/ radio group styled as stars +β”‚ β”œβ”€β”€ MyNotesPanel/ empty / saved / editing +β”‚ β”œβ”€β”€ StateMessage/ empty, error, (loading) +β”‚ └── AppShell/ shell + header + nav + footer +└── pages/ + β”œβ”€β”€ BrowsePage/ includes "Load more" + results count + β”œβ”€β”€ BookDetailPage/ includes hero, facts, description, ribbon, tags + └── ShelfPage/ includes page heading + sort +``` + +- Global CSS holds only tokens and element defaults. +- Every component owns its own `.module.css` and reads only `var(--token)` values, never raw hex. + +## Global CSS +|File|Purpose|Rules| +|----|-------|-----| +|index.css|Imports the three below in order (tokens, reset, base)|No rules of its own. Order matters because base uses tokens| +|tokens.css|Design decisions, as CSS variables|Only :root{--...}, with no selectors or layout. The only place for raw hex/px values. Name by role (`--color-accent`), not appearance (`--color-purple`). Add a token only when it's a real decision or reused. Keep it tidy and readable!| +|reset.css|Removes browser inconsistencies (box-sixing, margins, image behavior)|Tiny and generic. No colors or fonts, since "brand" values belong to tokens| +|base.css|Default look of plain HTML elements: body, headings, links, focus ring, `.visually-hidden`|Element selectors plus a few utilities only. If you write a specific class here, it belongs in a component module instead!| + + +## AppShell +**AppShell is in** (`components/AppShell/`). It's the frame around every page: header (logo, search, nav), the main area, and the footer. + +**For anyone building a page:** +- **Don't render a `
`, a header, a footer, or a search field in your page.** AppShell renders all of them, and your page appears inside its `
`. + +- **Don't set a page width or side padding.** AppShell owns a constant content width, so your page fills the space it's given. Vertical spacing inside the page is yours. + +- **Start your page with an `

`.** + +- **Routes:** AppShell is a layout route, so every page goes in as a child of it in `App.tsx` (`/`, `/shelf`, `/book/:id`). The current `App.tsx` has temporary placeholder pages you can swap out. + +- **On mobile, the nav is a fixed tab bar at the bottom.** The shell already leaves room for it, so you don't need to. + +## Search + +- The search field is in the header on every page. On submit it navigates to `/?q=`. An empty search goes to plain `/`. + +- Browse reads `q` from the URL with `useSearchParams`. There's no prop and no context. **The URL is the shared state.** + +- A new search drops the other params (genres, years, sort), so it starts fresh. + +- **Browse** is responsible for restarting the paging when `q` changes. + +- `SearchField` resets its text to match `q` when it changes (Back button, clearing), so the field always matches the URL. + +- **Param names must match exactly.** We have `genre` in one place and `genres` in another, so let's pick one. + +- When your filter panel updates the URL, keep `q` and the other params. Setting params naively replaces them all. + + + +## Icons +Every SVG now lives in `src/icons/` as a small component (``, optional `size` prop). Please add new icons there instead of inline in components. They use `currentColor`, so you color them with CSS. + +## Changelog + +- v1 (29.09.2026): created by Ilse +- v2 (02.10.2026): Added information about AppShell, Search and Icons. Updated folder structure to reflect relevant changes. \ No newline at end of file diff --git a/docs/styles_documentation.md b/docs/styles_documentation.md deleted file mode 100644 index c6f6acd..0000000 --- a/docs/styles_documentation.md +++ /dev/null @@ -1,33 +0,0 @@ -# Rules and information regarding styles, design, components - -## 1. Folder structure -``` -src/ -β”œβ”€β”€ styles/ tokens.css, reset.css, base.css, index.css -β”œβ”€β”€ components/ -β”‚ β”œβ”€β”€ Button/ 3 variants used throughout app -β”‚ β”œβ”€β”€ BookCover/ image, fallback, color pick -β”‚ β”œβ”€β”€ BookCard/ BookCard + ribbon + starsdisplay -β”‚ β”œβ”€β”€ BookGrid/ render auto grid of BookCard, with shelf option -β”‚ β”œβ”€β”€ SearchField/ draft state, submit on Enter or button -β”‚ β”œβ”€β”€ FilterPanel/ genre, year, clear, mobile toggle -β”‚ β”œβ”€β”€ RatingInput/ radio group styled as stars -β”‚ β”œβ”€β”€ MyNotesPanel/ empty / saved / editing -β”‚ β”œβ”€β”€ StateMessage/ empty, error, (loading) -β”‚ └── AppShell/ shell + header + nav + footer -└── pages/ - β”œβ”€β”€ BrowsePage/ includes "Load more" + results count - β”œβ”€β”€ BookDetailPage/ includes hero, facts, description, ribbon, tags - └── ShelfPage/ includes page heading + sort -``` - -- Global CSS holds only tokens and element defaults. -- Every component owns its own `.module.css` and reads only `var(--token)` values, never raw hex. - -## Global CSS -|File|Purpose|Rules| -|----|-------|-----| -|index.css|Imports the three below in order (tokens, reset, base)|No rules of its own. Order matters because base uses tokens| -|tokens.css|Design decisions, as CSS variables|Only :root{--...}, with no selectors or layout. The only place for raw hex/px values. Name by role (`--color-accent`), not appearance (`--color-purple`). Add a token only when it's a real decision or reused. Keep it tidy and readable!| -|reset.css|Removes browser inconsistencies (box-sixing, margins, image behavior)|Tiny and generic. No colors or fonts, since "brand" values belong to tokens| -|base.css|Default look of plain HTML elements: body, headings, links, focus ring, `.visually-hidden`|Element selectors plus a few utilities only. If you write a specific class here, it belongs in a component module instead!| From d08038ea553854d7e017bf90c8486e56740616ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Fri, 2 Oct 2026 09:41:40 +0200 Subject: [PATCH 6/7] feat: add more icons and update files that use them Icons now available: - Star (rating) - Chevron (back) - Filter - Logo (book with sparkle) - Ribbon (bookmark, save to shelf) - Search (looking glass) - Shelf (bookmark, navigation button) - Sparkle (decoration) - Browse (book, navigation button) --- client/src/components/BookCard/BookCard.tsx | 17 ++++----------- client/src/components/BookCover/BookCover.tsx | 8 ++----- client/src/icons/ChevronIcon.tsx | 20 ++++++++++++++++++ client/src/icons/FilterIcon.tsx | 19 +++++++++++++++++ client/src/icons/LogoIcon.tsx | 1 - client/src/icons/RibbonIcon.tsx | 10 +++++++++ client/src/icons/SearchIcons.tsx | 1 - client/src/icons/ShelfIcon.tsx | 1 - client/src/icons/SparkleIcon.tsx | 21 +++++++++++++++++++ client/src/icons/StarIcon.tsx | 15 +++++++++++++ client/src/styles/tokens.css | 2 ++ 11 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 client/src/icons/ChevronIcon.tsx create mode 100644 client/src/icons/FilterIcon.tsx create mode 100644 client/src/icons/RibbonIcon.tsx create mode 100644 client/src/icons/SparkleIcon.tsx create mode 100644 client/src/icons/StarIcon.tsx diff --git a/client/src/components/BookCard/BookCard.tsx b/client/src/components/BookCard/BookCard.tsx index 6461e67..decd191 100644 --- a/client/src/components/BookCard/BookCard.tsx +++ b/client/src/components/BookCard/BookCard.tsx @@ -2,6 +2,8 @@ import { Link } from 'react-router-dom'; import type { Book } from '../../types/book'; import { BookCover } from '../BookCover/BookCover'; import styles from './BookCard.module.css'; +import { StarIcon } from '../../icons/StarIcon'; +import { RibbonIcon } from '../../icons/RibbonIcon'; /* ---------- Types: the contract with BookGrid and the pages ---------- */ type BookCardProps = { @@ -66,16 +68,7 @@ function StarRow({ value }: { value: number }) { // One image for screen readers, so the five SVGs are hidden {[1, 2, 3, 4, 5].map((n) => ( - + ))} ); @@ -99,9 +92,7 @@ function FavoriteRibbon({ aria-label={`Save "${title}" to My shelf`} // ...so the label stays constant onClick={onToggle} > - + ); } diff --git a/client/src/components/BookCover/BookCover.tsx b/client/src/components/BookCover/BookCover.tsx index a9f2ef7..dda4490 100644 --- a/client/src/components/BookCover/BookCover.tsx +++ b/client/src/components/BookCover/BookCover.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import styles from './BookCover.module.css'; +import { SparkleIcon } from '../../icons/SparkleIcon'; const TONE_COUNT = 6; @@ -39,12 +40,7 @@ export function BookCover({ id, title, author, coverUrl, eager = false }: Props) aria-hidden="true" >
- - - + {title} {author && {author}} diff --git a/client/src/icons/ChevronIcon.tsx b/client/src/icons/ChevronIcon.tsx new file mode 100644 index 0000000..8ecc5a9 --- /dev/null +++ b/client/src/icons/ChevronIcon.tsx @@ -0,0 +1,20 @@ +type Props = { size?: number }; + +/* ---------- ChevronIcon: left chevron (Back to results) ---------- */ +export function ChevronIcon({ size = 20 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/FilterIcon.tsx b/client/src/icons/FilterIcon.tsx new file mode 100644 index 0000000..b1f6c90 --- /dev/null +++ b/client/src/icons/FilterIcon.tsx @@ -0,0 +1,19 @@ +type Props = { size?: number }; + +/* ---------- FilterIcon: three lines of decreasing length (mobile Filters button) ---------- */ +export function FilterIcon({ size = 16 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/LogoIcon.tsx b/client/src/icons/LogoIcon.tsx index be1e34e..2134ed7 100644 --- a/client/src/icons/LogoIcon.tsx +++ b/client/src/icons/LogoIcon.tsx @@ -1,4 +1,3 @@ -/* ---------- Props ---------- */ type Props = { size?: number }; /* ---------- LogoIcon: open book with a gold sparkle (header logo) ---------- */ diff --git a/client/src/icons/RibbonIcon.tsx b/client/src/icons/RibbonIcon.tsx new file mode 100644 index 0000000..de0e5bb --- /dev/null +++ b/client/src/icons/RibbonIcon.tsx @@ -0,0 +1,10 @@ +type Props = { width?: number; height?: number }; + +/* ---------- RibbonIcon: bookmark ribbon, fill/stroke are set by the parent's CSS ---------- */ +export function RibbonIcon({ width = 24, height = 40 }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/SearchIcons.tsx b/client/src/icons/SearchIcons.tsx index 9b4b7eb..415c23f 100644 --- a/client/src/icons/SearchIcons.tsx +++ b/client/src/icons/SearchIcons.tsx @@ -1,4 +1,3 @@ -/* ---------- Props ---------- */ type Props = { size?: number }; /* ---------- SearchIcon: magnifying glass (search button) ---------- */ diff --git a/client/src/icons/ShelfIcon.tsx b/client/src/icons/ShelfIcon.tsx index cb5a130..ddce6c6 100644 --- a/client/src/icons/ShelfIcon.tsx +++ b/client/src/icons/ShelfIcon.tsx @@ -1,4 +1,3 @@ -/* ---------- Props ---------- */ type Props = { size?: number }; /* ---------- ShelfIcon: bookmark (nav tab for My shelf) ---------- */ diff --git a/client/src/icons/SparkleIcon.tsx b/client/src/icons/SparkleIcon.tsx new file mode 100644 index 0000000..8476bb6 --- /dev/null +++ b/client/src/icons/SparkleIcon.tsx @@ -0,0 +1,21 @@ +/* ---------- SparkleIcon.tsx ---------- */ +type Props = { size?: number; className?: string }; + +/* ---------- SparkleIcon: four-pointed sparkle, color follows the parent's text color ---------- */ +// size is optional: leave it out when CSS (className) sets the width/height +export function SparkleIcon({ size, className }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/icons/StarIcon.tsx b/client/src/icons/StarIcon.tsx new file mode 100644 index 0000000..30ec101 --- /dev/null +++ b/client/src/icons/StarIcon.tsx @@ -0,0 +1,15 @@ +type Props = { size?: number; className?: string }; +/* ---------- StarIcon: rating star, color comes from the className's CSS (fill) ---------- */ +export function StarIcon({ size = 15, className }: Props) { + return ( + + ); +} \ No newline at end of file diff --git a/client/src/styles/tokens.css b/client/src/styles/tokens.css index c1a745e..f04842e 100644 --- a/client/src/styles/tokens.css +++ b/client/src/styles/tokens.css @@ -15,12 +15,14 @@ --color-ribbon: #c2810f; --color-highlight: #eeb34c; --color-star: #c78a12; + --color-star-off: #ead9d6; --color-chip-bg: #f8bbd2; --color-chip-text: #7a1f45; --color-shelf: #7b5535; --color-shelf-edge: #9a7050; --color-focus: #4a1f4f; --color-danger: #b3261e; + /* Generated book cover tones (bg / text pairs) */ --cover-0-bg: #6a2a5e; From f36f0ceeaba6565d8d19318523f054be5bba0b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilse=20=C3=85sbakk=20Resell?= Date: Fri, 2 Oct 2026 09:42:53 +0200 Subject: [PATCH 7/7] Feat: add row of stars that can show rating New component that will be used on detail page to let user set rating. Stars can be set to on/off with different colors depending on rating. --- .../StarRatingInput.module.css | 22 +++++++++++ .../StarRatingInput/StarRatingInput.tsx | 37 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 client/src/components/StarRatingInput/StarRatingInput.module.css create mode 100644 client/src/components/StarRatingInput/StarRatingInput.tsx diff --git a/client/src/components/StarRatingInput/StarRatingInput.module.css b/client/src/components/StarRatingInput/StarRatingInput.module.css new file mode 100644 index 0000000..203716e --- /dev/null +++ b/client/src/components/StarRatingInput/StarRatingInput.module.css @@ -0,0 +1,22 @@ +/* Row of buttons; the negative margin lines the first star up with the text above it */ +.group { + display: flex; + margin-left: -8px; +} + +/* 44x44 keeps the tap target large enough */ +.starButton { + width: 44px; + height: 44px; + background: none; + border: 0; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +/* Star colors (same values as in BookCard.module.css) */ +.starOn { fill: var(--color-star); } +.starOff { fill: var(--color-star-off); } \ No newline at end of file diff --git a/client/src/components/StarRatingInput/StarRatingInput.tsx b/client/src/components/StarRatingInput/StarRatingInput.tsx new file mode 100644 index 0000000..b658b14 --- /dev/null +++ b/client/src/components/StarRatingInput/StarRatingInput.tsx @@ -0,0 +1,37 @@ +import { StarIcon } from '../../icons/StarIcon'; +import styles from './StarRatingInput.module.css'; + +/* ---------- Use ------------ */ +/* + +*/ + +type StarRatingInputProps = { + value?: number; // current rating 1–5; undefined = not rated yet + onChange: (rating: number) => void; +}; + +/* ---------- StarRatingInput: editable rating (details page) ---------- */ +export function StarRatingInput({ value, onChange }: StarRatingInputProps) { + return ( +
+ {[1, 2, 3, 4, 5].map((n) => ( + + ))} +
+ ); +} \ No newline at end of file