fix: add newsletter box
This commit is contained in:
@@ -201,6 +201,8 @@ table {
|
|||||||
--color-push-box-bg: #ebeef4;
|
--color-push-box-bg: #ebeef4;
|
||||||
--color-card-dark-bg: #ebeef4;
|
--color-card-dark-bg: #ebeef4;
|
||||||
--color-button-primary: #951d51;
|
--color-button-primary: #951d51;
|
||||||
|
--color-purple: #951d51;
|
||||||
|
--color-purple-dark: #7a1842;
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
--site-header-height: 210px;
|
--site-header-height: 210px;
|
||||||
@@ -424,11 +426,15 @@ site-content {
|
|||||||
|
|
||||||
/* Category Grid */
|
/* Category Grid */
|
||||||
.category-grid {
|
.category-grid {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
flex-wrap: wrap;
|
||||||
|
align-items: stretch;
|
||||||
gap: var(--spacing-md, 1rem); /* 16px */
|
gap: var(--spacing-md, 1rem); /* 16px */
|
||||||
padding-bottom: var(--spacing-md, 1rem);
|
padding-bottom: var(--spacing-md, 1rem);
|
||||||
align-items: stretch;
|
}
|
||||||
|
|
||||||
|
.category-grid > * {
|
||||||
|
flex: 0 0 calc(50% - var(--spacing-md, 1rem) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
|
|||||||
22
index.html
22
index.html
@@ -232,6 +232,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<cta-button href="#">Toon alle onderwerpen</cta-button>
|
<cta-button href="#">Toon alle onderwerpen</cta-button>
|
||||||
</section>
|
</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-content>
|
||||||
|
|
||||||
<site-footer></site-footer>
|
<site-footer></site-footer>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import "./components/section-title.js";
|
|||||||
import "./components/add-to-cart-button.js";
|
import "./components/add-to-cart-button.js";
|
||||||
import "./components/cta-button.js";
|
import "./components/cta-button.js";
|
||||||
import "./components/category-card.js";
|
import "./components/category-card.js";
|
||||||
|
import "./components/newsletter-signup.js";
|
||||||
|
|
||||||
// App initialization (if needed)
|
// App initialization (if needed)
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|||||||
@@ -47,15 +47,14 @@ class CategoryCard extends HTMLElement {
|
|||||||
this.shadowRoot.innerHTML = `
|
this.shadowRoot.innerHTML = `
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: flex;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
flex: 1;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding: var(--spacing-md, 0.875rem) var(--spacing-xs, 0.25rem);
|
padding: var(--spacing-md, 0.875rem) var(--spacing-xs, 0.25rem);
|
||||||
text-decoration: none;
|
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() {
|
connectedCallback() {
|
||||||
this.render();
|
this.render();
|
||||||
|
// Use requestAnimationFrame to ensure slots are assigned
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.updateLogoVisibility();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback() {
|
attributeChangedCallback() {
|
||||||
this.render();
|
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() {
|
get backgroundColor() {
|
||||||
@@ -53,6 +80,10 @@ class PushBox extends HTMLElement {
|
|||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo-wrapper.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-wrapper ::slotted(img) {
|
.logo-wrapper ::slotted(img) {
|
||||||
width: 104px;
|
width: 104px;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -73,6 +104,12 @@ class PushBox extends HTMLElement {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cta-wrapper ::slotted(.cta-buttons) {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="push-box">
|
<div class="push-box">
|
||||||
<div class="logo-wrapper">
|
<div class="logo-wrapper">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Site Content Component
|
* Site Content Component
|
||||||
* Main content area wrapper with slot for page content
|
* Main content area wrapper with slot for page content
|
||||||
|
* Uses flexbox with gap for consistent vertical spacing
|
||||||
*/
|
*/
|
||||||
class SiteContent extends HTMLElement {
|
class SiteContent extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -22,8 +23,22 @@ class SiteContent extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: var(--spacing-md);
|
display: flex;
|
||||||
padding-bottom: var(--spacing-md);
|
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>
|
</style>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ export { searchIcon } from "./search.js";
|
|||||||
export { menuIcon } from "./menu.js";
|
export { menuIcon } from "./menu.js";
|
||||||
export { userIcon } from "./user.js";
|
export { userIcon } from "./user.js";
|
||||||
export { shoppingBagIcon } from "./shopping-bag.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