fix: add newsletter box
This commit is contained in:
@@ -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;
|
||||
|
||||
193
js/components/newsletter-signup.js
Normal file
193
js/components/newsletter-signup.js
Normal 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);
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user