aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2023-09-09 13:50:17 +0200
committerJoris2023-09-09 13:50:17 +0200
commit82429caf3c2886c2d94e09d020e645b06bd4680d (patch)
tree3183bf0c687c25e17fd36a23ced58fd1e6e29dda
parent08a5f3519f29cd486d7fe4c295e5d5c7f031104a (diff)
Allow to open book detail in modal
-rw-r--r--public/main.css107
-rw-r--r--src/book.ts2
-rw-r--r--src/main.ts10
-rw-r--r--src/view/books.ts61
-rw-r--r--src/view/components/modal.ts26
5 files changed, 196 insertions, 10 deletions
diff --git a/public/main.css b/public/main.css
index 09c344f..e5d3bcf 100644
--- a/public/main.css
+++ b/public/main.css
@@ -1,3 +1,19 @@
+/* Variables */
+
+:root {
+ --color-focus: #888833;
+
+ --width-close-button: 4rem;
+
+ --font-size-dog: 1.5rem;
+
+ --spacing-beetle: 0.25rem;
+ --spacing-mouse: 0.5rem;
+ --spacing-cat: 1rem;
+}
+
+/* Top level */
+
html {
height: 100%;
}
@@ -9,6 +25,51 @@ body {
font-family: sans-serif;
}
+/* Modal */
+
+.g-Modal {
+ width: 100vw;
+ height: 100vh;
+ position: fixed;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(0, 0, 0, 0.4);
+ z-index: 1;
+}
+
+.g-Modal__Close {
+ position: absolute;
+ top: 2rem;
+ right: 2rem;
+ cursor: pointer;
+ font-weight: bold;
+ border-radius: 50%;
+ border: 1px solid #EEE;
+ background-color: transparent;
+ width: var(--width-close-button);
+ height: var(--width-close-button);
+ font-size: 2rem;
+ font-family: monospace;
+}
+
+.g-Modal__Close:hover, .g-Modal__Close:focus {
+ background-color: #EEE;
+}
+
+.g-Modal__Content {
+ position: relative;
+ background-color: white;
+ width: 50%;
+ height: 50%;
+ border-radius: 0.2rem;
+ border: 1px solid #EEE;
+ padding: 2rem;
+ padding-right: calc(2rem + var(--width-close-button) + 2rem);
+}
+
/* Filters */
aside {
@@ -50,7 +111,7 @@ ul {
}
.g-Filter button:hover {
- background-color: #888833;
+ background-color: var(--color-focus);
}
/* Books */
@@ -78,7 +139,49 @@ header {
}
.g-Book {
- width: 13rem;
align-self: center;
border: 1px solid #DDDDDD;
+ padding: 0;
+ width: fit-content;
+}
+
+.g-Book:hover, .g-Book:focus {
+ cursor: pointer;
+ outline: none;
+}
+
+.g-Book:hover .g-Book__Image, .g-Book:focus .g-Book__Image {
+ transform: scale(105%);
+ opacity: 0.9;
+}
+
+.g-Book__Image {
+ display: flex;
+ width: 13rem;
+ transition: all 0.2s ease-in-out;
+}
+
+/* Book detail */
+
+.g-BookDetail {
+ display: flex;
+ justify-content: space-between;
+}
+
+.g-BookDetail img {
+ width: 10rem;
+}
+
+.g-BookDetail__Title {
+ font-size: var(--font-size-dog);
+ margin-bottom: var(--spacing-beetle);
+}
+
+.g-BookDetail__Subtitle {
+ font-style: italic;
+ margin-bottom: var(--spacing-beetle);
+}
+
+.g-BookDetail__Genres {
+ margin-top: var(--spacing-cat);
}
diff --git a/src/book.ts b/src/book.ts
index a5ddb75..328f8e1 100644
--- a/src/book.ts
+++ b/src/book.ts
@@ -1,6 +1,6 @@
export interface Book {
title: string
- subtitle: string
+ subtitle?: string
authors: Array<string>
authorsSort: string
genres: Array<string>
diff --git a/src/main.ts b/src/main.ts
index 21bf215..5885871 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,8 +1,9 @@
-import { h, withVar, mount } from 'lib/rx'
+import { h, withVar, mount, Html } from 'lib/rx'
import * as Search from 'lib/search'
import * as Functions from 'lib/functions'
import * as I18n from 'lib/i18n'
import * as Filters from 'view/filters'
+import * as Books from 'view/books'
import * as Book from 'book'
// @ts-ignore
@@ -34,12 +35,7 @@ mount(withVar<Filters.Model>({}, (filters, updateFilters) =>
),
filteredBooks.map(fb => I18n.unit(fb.length, 'livre', 'livres'))
),
- h('div',
- { className: 'g-Books' },
- filteredBooks.map(fb =>
- fb.map(book => h('img', { className: 'g-Book', src: book.cover }))
- )
- )
+ Books.view(filteredBooks)
)
]
})
diff --git a/src/view/books.ts b/src/view/books.ts
new file mode 100644
index 0000000..6ea0c12
--- /dev/null
+++ b/src/view/books.ts
@@ -0,0 +1,61 @@
+import { h, withVar, mount, Html, Rx } from 'lib/rx'
+import * as Book from 'book'
+import * as Modal from 'view/components/modal'
+
+export function view(books: Rx<Array<Book.Book>>): Html {
+ return h('div',
+ { className: 'g-Books' },
+ withVar<Book.Book | undefined>(undefined, (focusBook, updateFocusBook) => [
+ focusBook.map(book => book && bookDetailModal({
+ book,
+ onClose: () => updateFocusBook(_ => undefined)
+ })),
+ books.map(bs => bs.map(book => viewBook({
+ book,
+ onSelect: (book) => updateFocusBook(_ => book)
+ })))
+ ])
+ )
+}
+
+interface ViewBookParams {
+ book: Book.Book
+ onSelect: (book: Book.Book) => void
+}
+
+function viewBook({ book, onSelect }: ViewBookParams): Html {
+ return h('button',
+ { className: 'g-Book' },
+ h('img',
+ { src: book.cover,
+ className: 'g-Book__Image',
+ onclick: () => onSelect(book)
+ }
+ )
+ )
+}
+
+interface BookDetailModalParams {
+ book: Book.Book
+ onClose: () => void
+}
+
+function bookDetailModal({ book, onClose }: BookDetailModalParams): Html {
+ return Modal.view({
+ content: h('div',
+ { className: 'g-BookDetail' },
+ h('div',
+ h('div', { className: 'g-BookDetail__Title' }, `${book.title}, ${book.date}`),
+ book.subtitle && h('div', { className: 'g-BookDetail__Subtitle' }, book.subtitle),
+ book.authors.join(', '),
+ h('div',
+ { className: 'g-BookDetail__Genres' },
+ book.genres.length > 1 ? 'Genres : ' : 'Genre : ',
+ book.genres.join(', ')
+ )
+ ),
+ h('img', { src: book.cover })
+ ),
+ onClose
+ })
+}
diff --git a/src/view/components/modal.ts b/src/view/components/modal.ts
new file mode 100644
index 0000000..fe08272
--- /dev/null
+++ b/src/view/components/modal.ts
@@ -0,0 +1,26 @@
+import { h, Html } from 'lib/rx'
+
+interface Params {
+ content: Html,
+ onClose: () => void
+}
+
+export function view({ content, onClose }: Params): Html {
+ return h('div',
+ { className: 'g-Modal',
+ onclick: () => onClose()
+ },
+ h('div',
+ { className: 'g-Modal__Content',
+ onclick: (e: Event) => e.stopPropagation()
+ },
+ h('button',
+ { className: 'g-Modal__Close',
+ onclick: () => onClose()
+ },
+ '✖'
+ ),
+ content
+ )
+ )
+}