fix: add newsletter box
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
31
js/icons/send-icon.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user