410 lines
10 KiB
JavaScript
410 lines
10 KiB
JavaScript
/**
|
|
* Book Details Component
|
|
* Displays book information with cover, metadata, and purchase buttons
|
|
*/
|
|
|
|
class BookDetails extends HTMLElement {
|
|
static get observedAttributes() {
|
|
return [
|
|
"title",
|
|
"author",
|
|
"author-href",
|
|
"price",
|
|
"image",
|
|
"isbn",
|
|
"format",
|
|
"delivery",
|
|
"categories",
|
|
"ebook-available",
|
|
];
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.attachShadow({ mode: "open" });
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
attributeChangedCallback() {
|
|
if (this.shadowRoot) {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const addToCartBtn = this.shadowRoot?.querySelector(".btn-cart");
|
|
const ebookBtn = this.shadowRoot?.querySelector(".btn-ebook");
|
|
const favoriteBtn = this.shadowRoot?.querySelector(".favorite-btn");
|
|
|
|
if (addToCartBtn) {
|
|
addToCartBtn.addEventListener("button-click", () => {
|
|
this.dispatchEvent(
|
|
new CustomEvent("add-to-cart", {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {
|
|
title: this.bookTitle,
|
|
price: this.price,
|
|
type: "physical",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
if (ebookBtn) {
|
|
ebookBtn.addEventListener("button-click", () => {
|
|
this.dispatchEvent(
|
|
new CustomEvent("buy-ebook", {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {
|
|
title: this.bookTitle,
|
|
type: "ebook",
|
|
},
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
if (favoriteBtn) {
|
|
favoriteBtn.addEventListener("click", () => {
|
|
favoriteBtn.classList.toggle("active");
|
|
this.dispatchEvent(
|
|
new CustomEvent("toggle-favorite", {
|
|
bubbles: true,
|
|
composed: true,
|
|
detail: {
|
|
title: this.bookTitle,
|
|
},
|
|
})
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
get bookTitle() {
|
|
return this.getAttribute("title") || "Book Title";
|
|
}
|
|
|
|
get author() {
|
|
return this.getAttribute("author") || "Author Name";
|
|
}
|
|
|
|
get authorHref() {
|
|
return this.getAttribute("author-href") || "#";
|
|
}
|
|
|
|
get price() {
|
|
return this.getAttribute("price") || "€ 0,00";
|
|
}
|
|
|
|
get image() {
|
|
return this.getAttribute("image") || "";
|
|
}
|
|
|
|
get isbn() {
|
|
return this.getAttribute("isbn") || "";
|
|
}
|
|
|
|
get format() {
|
|
return this.getAttribute("format") || "Paperback";
|
|
}
|
|
|
|
get delivery() {
|
|
return this.getAttribute("delivery") || "Direct leverbaar";
|
|
}
|
|
|
|
get categories() {
|
|
return this.getAttribute("categories") || "";
|
|
}
|
|
|
|
get ebookAvailable() {
|
|
return this.hasAttribute("ebook-available");
|
|
}
|
|
|
|
renderCategories() {
|
|
if (!this.categories) return "";
|
|
|
|
// Categories are comma-separated, with optional href in format: "Name|href,Name2|href2"
|
|
const cats = this.categories.split(",").map((cat) => {
|
|
const [name, href] = cat.trim().split("|");
|
|
if (href) {
|
|
return `<a href="${href}" class="category-link">${name}</a>`;
|
|
}
|
|
return `<span class="category-text">${name}</span>`;
|
|
});
|
|
|
|
return cats.join('<span class="category-separator"> / </span>');
|
|
}
|
|
|
|
render() {
|
|
const imageHtml = this.image
|
|
? `<img src="${this.image}" alt="${this.bookTitle}" class="book-cover">`
|
|
: `<div class="book-cover placeholder">
|
|
<book-open-icon size="48" color="var(--color-text-light)"></book-open-icon>
|
|
</div>`;
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
<style>
|
|
:host {
|
|
display: block;
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
}
|
|
|
|
.book-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-md, 1rem);
|
|
}
|
|
|
|
/* Header Section */
|
|
.header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm, 0.5rem);
|
|
}
|
|
|
|
.title-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 0.5rem);
|
|
}
|
|
|
|
.title {
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
line-height: 34px;
|
|
color: var(--color-text, #1e293b);
|
|
margin: 0;
|
|
}
|
|
|
|
.favorite-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.favorite-btn svg {
|
|
width: 28px;
|
|
height: 28px;
|
|
transition: transform var(--transition-fast, 150ms ease);
|
|
}
|
|
|
|
.favorite-btn:hover svg {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.favorite-btn.active svg {
|
|
fill: #f59e0b;
|
|
}
|
|
|
|
.author-link {
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
font-size: 16px;
|
|
font-weight: 300;
|
|
line-height: 24px;
|
|
color: var(--color-purple, #951d51);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.author-link:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.price {
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
line-height: 24px;
|
|
color: var(--color-text, #1e293b);
|
|
margin: 0;
|
|
}
|
|
|
|
/* Content Section */
|
|
.content {
|
|
display: flex;
|
|
gap: var(--spacing-md, 1rem);
|
|
}
|
|
|
|
.cover-container {
|
|
flex-shrink: 0;
|
|
width: 110px;
|
|
height: 177px;
|
|
}
|
|
|
|
.book-cover {
|
|
width: 110px;
|
|
height: 177px;
|
|
object-fit: cover;
|
|
border-radius: var(--radius-sm, 0.25rem);
|
|
}
|
|
|
|
.book-cover.placeholder {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: var(--color-background-tertiary, #f1f5f9);
|
|
border-radius: var(--radius-sm, 0.25rem);
|
|
}
|
|
|
|
/* Details Grid */
|
|
.details {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm, 0.5rem);
|
|
}
|
|
|
|
.detail-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: var(--spacing-sm, 0.5rem);
|
|
}
|
|
|
|
.detail-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0;
|
|
}
|
|
|
|
.detail-item.full-width {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.detail-label {
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
font-size: 12px;
|
|
font-weight: 200;
|
|
line-height: 24px;
|
|
color: var(--color-purple, #951d51);
|
|
}
|
|
|
|
.detail-value {
|
|
font-family: var(--font-family-outfit, "Outfit", sans-serif);
|
|
font-size: 16px;
|
|
font-weight: 300;
|
|
line-height: 24px;
|
|
color: var(--color-text, #1e293b);
|
|
}
|
|
|
|
.category-link {
|
|
color: var(--color-text, #1e293b);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.category-link:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.category-text {
|
|
color: var(--color-text, #1e293b);
|
|
}
|
|
|
|
.category-separator {
|
|
color: var(--color-text, #1e293b);
|
|
}
|
|
|
|
/* Buttons Section */
|
|
.buttons {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-sm, 0.5rem);
|
|
}
|
|
</style>
|
|
|
|
<div class="book-details">
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<div class="title-row">
|
|
<h1 class="title">${this.bookTitle}</h1>
|
|
<button class="favorite-btn" aria-label="Toevoegen aan favorieten">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<a href="${this.authorHref}" class="author-link">${this.author}</a>
|
|
<p class="price">${this.price}</p>
|
|
</div>
|
|
|
|
<!-- Content: Cover + Details -->
|
|
<div class="content">
|
|
<div class="cover-container">
|
|
${imageHtml}
|
|
</div>
|
|
<div class="details">
|
|
${
|
|
this.categories
|
|
? `
|
|
<div class="detail-item full-width">
|
|
<span class="detail-label">Categorieën</span>
|
|
<span class="detail-value">${this.renderCategories()}</span>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
<div class="detail-row">
|
|
${
|
|
this.isbn
|
|
? `
|
|
<div class="detail-item">
|
|
<span class="detail-label">ISBN</span>
|
|
<span class="detail-value">${this.isbn}</span>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
<div class="detail-item">
|
|
<span class="detail-label">Uitvoering</span>
|
|
<span class="detail-value">${this.format}</span>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<div class="detail-item">
|
|
<span class="detail-label">Uitvoering</span>
|
|
<span class="detail-value">${this.format}</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">Levertijd</span>
|
|
<span class="detail-value">${this.delivery}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Buttons -->
|
|
<div class="buttons">
|
|
<icon-cta-button class="btn-cart" icon="cart" variant="primary">
|
|
In winkelwagen
|
|
</icon-cta-button>
|
|
${
|
|
this.ebookAvailable
|
|
? `
|
|
<icon-cta-button class="btn-ebook" icon="ebook" variant="secondary">
|
|
Koop eBook
|
|
</icon-cta-button>
|
|
`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
customElements.define("book-details", BookDetails);
|