fix: add newsletter box

This commit is contained in:
Tim Rijkse
2026-01-15 15:28:14 +01:00
parent 968d2036b4
commit 1a6b47c8d3
9 changed files with 313 additions and 8 deletions

View File

@@ -201,6 +201,8 @@ table {
--color-push-box-bg: #ebeef4;
--color-card-dark-bg: #ebeef4;
--color-button-primary: #951d51;
--color-purple: #951d51;
--color-purple-dark: #7a1842;
/* Layout */
--site-header-height: 210px;
@@ -424,11 +426,15 @@ site-content {
/* Category Grid */
.category-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: var(--spacing-md, 1rem); /* 16px */
padding-bottom: var(--spacing-md, 1rem);
align-items: stretch;
}
.category-grid > * {
flex: 0 0 calc(50% - var(--spacing-md, 1rem) / 2);
}
/* ==========================================================================

View File

@@ -232,6 +232,28 @@
</div>
<cta-button href="#">Toon alle onderwerpen</cta-button>
</section>
<div class="content-padding">
<push-box>
<h2 slot="title">
Gespecialiseerd op het vlak van boeddhisme en aanverwante
Oost-West thema's
</h2>
<div slot="cta" class="cta-buttons">
<arrow-button href="#">Klantenservice</arrow-button>
<arrow-button href="#">Neem contact op</arrow-button>
</div>
</push-box>
</div>
<div class="content-padding">
<newsletter-signup
title="Blijf op de hoogte"
description="Schrijf je in voor onze nieuwsbrief en ontvang het laatste nieuws over nieuwe boeken en aanbiedingen."
button-text="Inschrijven"
placeholder="Je e-mailadres"
></newsletter-signup>
</div>
</site-content>
<site-footer></site-footer>

View File

@@ -17,6 +17,7 @@ import "./components/section-title.js";
import "./components/add-to-cart-button.js";
import "./components/cta-button.js";
import "./components/category-card.js";
import "./components/newsletter-signup.js";
// App initialization (if needed)
document.addEventListener("DOMContentLoaded", () => {

View File

@@ -47,15 +47,14 @@ class CategoryCard extends HTMLElement {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
height: 100%;
display: flex;
}
.card {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
flex: 1;
color: inherit;
padding: var(--spacing-md, 0.875rem) var(--spacing-xs, 0.25rem);
text-decoration: none;

View File

@@ -0,0 +1,193 @@
/**
* Newsletter Signup Component
* A newsletter subscription form with title, description, and email input
*/
import { sendIcon } from "../icons/send-icon.js";
class NewsletterSignup extends HTMLElement {
static get observedAttributes() {
return ["title", "description", "button-text", "placeholder"];
}
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
attributeChangedCallback() {
this.render();
this.setupEventListeners();
}
get title() {
return this.getAttribute("title") || "Blijf op de hoogte";
}
get description() {
return (
this.getAttribute("description") ||
"Schrijf je in voor onze nieuwsbrief en ontvang het laatste nieuws over nieuwe boeken en aanbiedingen."
);
}
get buttonText() {
return this.getAttribute("button-text") || "Inschrijven";
}
get placeholder() {
return this.getAttribute("placeholder") || "Je e-mailadres";
}
setupEventListeners() {
const form = this.shadowRoot?.querySelector("form");
if (form) {
form.addEventListener("submit", (e) => {
e.preventDefault();
const emailInput = this.shadowRoot.querySelector('input[type="email"]');
const email = emailInput?.value;
if (email) {
// Dispatch custom event for parent components to handle
this.dispatchEvent(
new CustomEvent("newsletter-submit", {
detail: { email },
bubbles: true,
composed: true,
})
);
// Reset form
form.reset();
// Show feedback (you could enhance this)
console.log("Newsletter signup:", email);
}
});
}
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.newsletter-box {
border: 1px solid var(--color-newsletter-border, #951D51);
border-radius: 4px;
padding: 24px 16px;
background: transparent;
}
.newsletter-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.newsletter-title {
font-family: var(--font-family-outfit, "Outfit", sans-serif);
font-size: 24px;
font-weight: 700;
line-height: 34px;
color: var(--color-newsletter-title, #951D51);
margin: 0;
}
.newsletter-description {
font-family: var(--font-family-outfit, "Outfit", sans-serif);
font-size: 16px;
font-weight: 400;
line-height: 32px;
color: #000000;
margin: 0;
}
.newsletter-form {
display: flex;
gap: 16px;
}
.newsletter-input {
flex: 1 1 auto;
min-width: 0;
padding: 12px 16px;
font-family: var(--font-family-outfit, "Outfit", sans-serif);
font-size: 16px;
line-height: 24px;
border: 1px solid var(--color-newsletter-border, #951D51);
border-radius: 4px;
background-color: #ffffff;
outline: none;
transition: border-color 150ms ease;
}
.newsletter-input::placeholder {
color: #9CA3AF;
}
.newsletter-input:focus {
border-color: var(--color-newsletter-border, #951D51);
box-shadow: 0 0 0 1px var(--color-newsletter-border, #951D51);
}
.newsletter-button {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 48px;
padding: 0;
color: #ffffff;
background-color: var(--color-newsletter-button, #951D51);
border: 1px solid var(--color-newsletter-button, #951D51);
border-radius: 4px;
cursor: pointer;
transition: background-color 150ms ease, border-color 150ms ease;
}
.newsletter-button svg {
width: 24px;
height: 24px;
}
.newsletter-button:hover {
background-color: var(--color-newsletter-button-hover, #7a1842);
border-color: var(--color-newsletter-button-hover, #7a1842);
}
.newsletter-button:focus {
outline: 2px solid var(--color-newsletter-button, #951D51);
outline-offset: 2px;
}
</style>
<div class="newsletter-box">
<div class="newsletter-content">
<h2 class="newsletter-title">${this.title}</h2>
<p class="newsletter-description">${this.description}</p>
<form class="newsletter-form">
<input
type="email"
class="newsletter-input"
placeholder="${this.placeholder}"
required
aria-label="E-mailadres"
/>
<button type="submit" class="newsletter-button" aria-label="${this.buttonText || 'Inschrijven'}">
${sendIcon({ size: 24, color: "#ffffff" })}
</button>
</form>
</div>
</div>
`;
}
}
customElements.define("newsletter-signup", NewsletterSignup);

View File

@@ -15,10 +15,37 @@ class PushBox extends HTMLElement {
connectedCallback() {
this.render();
// Use requestAnimationFrame to ensure slots are assigned
requestAnimationFrame(() => {
this.updateLogoVisibility();
});
}
attributeChangedCallback() {
this.render();
requestAnimationFrame(() => {
this.updateLogoVisibility();
});
}
updateLogoVisibility() {
const logoSlot = this.shadowRoot?.querySelector('slot[name="logo"]');
const logoWrapper = this.shadowRoot?.querySelector(".logo-wrapper");
if (logoSlot && logoWrapper) {
const assignedNodes = logoSlot.assignedNodes();
const hasContent =
assignedNodes.length > 0 &&
assignedNodes.some((node) => {
return node.nodeType === Node.ELEMENT_NODE && node.tagName === "IMG";
});
if (!hasContent) {
logoWrapper.classList.add("hidden");
} else {
logoWrapper.classList.remove("hidden");
}
}
}
get backgroundColor() {
@@ -53,6 +80,10 @@ class PushBox extends HTMLElement {
margin-bottom: 32px;
}
.logo-wrapper.hidden {
display: none;
}
.logo-wrapper ::slotted(img) {
width: 104px;
height: auto;
@@ -73,6 +104,12 @@ class PushBox extends HTMLElement {
font-size: 16px;
font-weight: 400;
}
.cta-wrapper ::slotted(.cta-buttons) {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
</style>
<div class="push-box">
<div class="logo-wrapper">

View File

@@ -1,6 +1,7 @@
/**
* Site Content Component
* Main content area wrapper with slot for page content
* Uses flexbox with gap for consistent vertical spacing
*/
class SiteContent extends HTMLElement {
constructor() {
@@ -22,8 +23,22 @@ class SiteContent extends HTMLElement {
}
.content {
padding-top: var(--spacing-md);
padding-bottom: var(--spacing-md);
display: flex;
flex-direction: column;
gap: var(--spacing-md, 16px);
padding-top: var(--spacing-md, 16px);
padding-bottom: var(--spacing-md, 16px);
}
/* Remove vertical padding from direct children to let gap handle spacing */
::slotted(.content-padding) {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
::slotted(.section) {
padding-top: var(--spacing-md, 16px);
padding-bottom: var(--spacing-md, 16px);
}
</style>
<main class="content">

View File

@@ -7,3 +7,4 @@ export { searchIcon } from "./search.js";
export { menuIcon } from "./menu.js";
export { userIcon } from "./user.js";
export { shoppingBagIcon } from "./shopping-bag.js";
export { sendIcon } from "./send-icon.js";

31
js/icons/send-icon.js Normal file
View File

@@ -0,0 +1,31 @@
/**
* Send Icon (Lucide)
* @param {Object} props - Icon properties
* @param {number} props.size - Icon size (default: 24)
* @param {string} props.color - Icon color (default: currentColor)
* @param {number} props.strokeWidth - Stroke width (default: 2)
* @returns {string} SVG string
*/
export function sendIcon({
size = 24,
color = "currentColor",
strokeWidth = 2,
} = {}) {
return `
<svg
xmlns="http://www.w3.org/2000/svg"
width="${size}"
height="${size}"
viewBox="0 0 24 24"
fill="none"
stroke="${color}"
stroke-width="${strokeWidth}"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-send-icon lucide-send"
>
<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/>
<path d="m21.854 2.147-10.94 10.939"/>
</svg>
`;
}