From 945f75f1aeb689c8ac8ac16815443ed67121af80 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sun, 10 Aug 2025 12:48:49 +0000 Subject: [PATCH] feat(AEMCP-100): Apply AI-generated changes [skip ci] --- blocks/commerce-teaser/_commerce-teaser.json | 89 +++++++++++++ blocks/commerce-teaser/commerce-teaser.css | 103 +++++++++++++++ blocks/commerce-teaser/commerce-teaser.js | 125 +++++++++++++++++++ component-definition.json | 16 +++ component-filters.json | 3 +- component-models.json | 65 ++++++++++ models/_section.json | 3 +- 7 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 blocks/commerce-teaser/_commerce-teaser.json create mode 100644 blocks/commerce-teaser/commerce-teaser.css create mode 100644 blocks/commerce-teaser/commerce-teaser.js diff --git a/blocks/commerce-teaser/_commerce-teaser.json b/blocks/commerce-teaser/_commerce-teaser.json new file mode 100644 index 0000000..db4c427 --- /dev/null +++ b/blocks/commerce-teaser/_commerce-teaser.json @@ -0,0 +1,89 @@ +{ + "definitions": [ + { + "title": "Commerce Teaser", + "id": "commerce-teaser", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Commerce Teaser", + "model": "commerce-teaser", + "filter": "commerce-teaser" + } + } + } + } + } + ], + "models": [ + { + "id": "commerce-teaser", + "fields": [ + { + "component": "text", + "name": "title", + "value": "", + "label": "Product Title" + }, + { + "component": "text", + "name": "price", + "value": "", + "label": "Price", + "description": "Include currency symbol, e.g., $49.99" + }, + { + "component": "richtext", + "name": "description", + "value": "", + "label": "Product Description" + }, + { + "component": "reference", + "name": "image", + "label": "Product Image", + "valueType": "string", + "multi": false + }, + { + "component": "text", + "name": "imageAlt", + "value": "", + "label": "Image Alt Text" + }, + { + "component": "text", + "name": "link", + "value": "", + "label": "Product Link" + }, + { + "component": "text", + "name": "label", + "value": "Shop Now", + "label": "Button Label" + }, + { + "component": "select", + "name": "target", + "value": "_blank", + "label": "Link Target", + "options": [ + { + "name": "New Window (_blank)", + "value": "_blank" + }, + { + "name": "Same Window (_self)", + "value": "" + } + ] + } + ] + } + ], + "filters": [] +} + diff --git a/blocks/commerce-teaser/commerce-teaser.css b/blocks/commerce-teaser/commerce-teaser.css new file mode 100644 index 0000000..46fb673 --- /dev/null +++ b/blocks/commerce-teaser/commerce-teaser.css @@ -0,0 +1,103 @@ +/* Commerce Teaser Block Styles */ + +.commerce-teaser { + display: flex; + flex-direction: column; + border: 1px solid rgba(var(--text-color), 0.1); + border-radius: 8px; + overflow: hidden; + background: rgb(var(--background-color)); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.commerce-teaser:hover { + transform: translateY(-3px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +/* Image */ +.commerce-teaser-image { + position: relative; + aspect-ratio: 4 / 3; + width: 100%; + overflow: hidden; +} + +.commerce-teaser-image picture, +.commerce-teaser-image img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +/* Content */ +.commerce-teaser-content { + padding: var(--spacing-m); + display: flex; + flex-direction: column; + gap: var(--spacing-s); +} + +.commerce-teaser-title { + font-size: var(--heading-font-size-m); + font-weight: var(--font-bold); + margin: 0; +} + +.commerce-teaser-price { + font-size: calc(var(--heading-font-size-s) * 1.1); + font-weight: var(--font-bold); + color: rgb(var(--button-primary)); + margin: 0; +} + +.commerce-teaser-description { + font-size: var(--body-font-size-s); + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; /* show up to 3 lines */ + -webkit-box-orient: vertical; +} + +.commerce-teaser-button-container { + margin-top: auto; +} + +.commerce-teaser-button-container .button { + display: inline-block; + padding: var(--btn-padding); + background: rgb(var(--button-primary)); + color: rgb(var(--on-button-primary)); + border-radius: 4px; + text-decoration: none; + font-weight: var(--font-bold); + transition: background 0.3s ease, color 0.3s ease; +} + +.commerce-teaser-button-container .button:hover, +.commerce-teaser-button-container .button:focus { + background: rgb(var(--button-primary-hover)); + color: rgb(var(--on-button-primary)); +} + +/* Responsive */ +@media (width >= 768px) { + .commerce-teaser { + flex-direction: row; + max-height: 320px; + } + + .commerce-teaser-image { + flex: 0 0 40%; + aspect-ratio: unset; + height: 100%; + } + + .commerce-teaser-content { + flex: 1; + } +} + diff --git a/blocks/commerce-teaser/commerce-teaser.js b/blocks/commerce-teaser/commerce-teaser.js new file mode 100644 index 0000000..546d6c4 --- /dev/null +++ b/blocks/commerce-teaser/commerce-teaser.js @@ -0,0 +1,125 @@ +import { setBlockItemOptions, moveClassToTargetedChild } from '../../scripts/utils.js'; +import { createOptimizedPicture } from '../../scripts/aem.js'; +import { moveInstrumentation } from '../../scripts/scripts.js'; +import { renderButton } from '../../components/button/button.js'; + +/* + * Commerce-Teaser Block + * + * Authoring column order: + * 0 — Product Title (text) + * 1 — Price (text, supports currency formatting) + * 2 — Description (rich-text / text) + * 3 — Product Image (asset reference or ) + * 4 — Image Alt Text (text – optional) + * 5 — Product Link (URL) + * 6 — Button Label (text – optional, defaults to "Shop Now") + * 7 — Link Target (select – optional, _blank opens new window) + */ + +export default function decorate(block) { + /* ---------------- Parse configuration ---------------- */ + const blockItemsOptions = []; + const blockItemMap = [ + { name: 'title' }, + { name: 'price' }, + { name: 'description' }, + { name: 'image' }, + { name: 'imageAlt' }, + { name: 'link' }, + { name: 'label' }, + { name: 'target' }, + ]; + + setBlockItemOptions(block, blockItemMap, blockItemsOptions); + const config = blockItemsOptions[0] || {}; + + /* ---------------- Build DOM ---------------- */ + const wrapper = document.createElement('div'); + wrapper.className = 'commerce-teaser'; + + /* Image */ + const imageContainer = document.createElement('div'); + imageContainer.className = 'commerce-teaser-image'; + + if (config.image) { + let optimizedPicture; + try { + optimizedPicture = createOptimizedPicture( + config.image, + config.imageAlt || config.title || 'Product image', + true, + [{ width: '750' }], + ); + } catch (e) { + // When config.image is already a + optimizedPicture = config.image.cloneNode(true); + } + imageContainer.appendChild(optimizedPicture); + } else { + const placeholder = createOptimizedPicture( + 'https://placehold.co/600x450?text=No+Image', + config.imageAlt || 'Placeholder product image', + true, + [{ width: '750' }], + ); + imageContainer.appendChild(placeholder); + } + + /* Content */ + const contentContainer = document.createElement('div'); + contentContainer.className = 'commerce-teaser-content'; + + if (config.title) { + const titleEl = document.createElement('h3'); + titleEl.className = 'commerce-teaser-title'; + titleEl.textContent = config.title; + contentContainer.appendChild(titleEl); + } + + if (config.price) { + const priceEl = document.createElement('p'); + priceEl.className = 'commerce-teaser-price'; + priceEl.textContent = config.price; + priceEl.setAttribute('aria-label', `Price: ${config.price}`); + contentContainer.appendChild(priceEl); + } + + if (config.description) { + const descEl = document.createElement('p'); + descEl.className = 'commerce-teaser-description'; + descEl.textContent = config.description; + contentContainer.appendChild(descEl); + } + + if (config.link) { + const buttonContainer = document.createElement('div'); + buttonContainer.className = 'commerce-teaser-button-container'; + + const anchor = document.createElement('a'); + anchor.href = config.link; + + const button = renderButton({ + linkButton: anchor, + linkText: config.label || 'Shop Now', + linkTitle: config.title || 'View Product', + linkTarget: config.target || '_blank', + linkType: '', + linkStyle: '', + }); + + buttonContainer.appendChild(button); + moveClassToTargetedChild(block, button); + contentContainer.appendChild(buttonContainer); + } + + /* Assemble */ + wrapper.appendChild(imageContainer); + wrapper.appendChild(contentContainer); + + moveInstrumentation(block, wrapper); + + block.textContent = ''; + block.appendChild(wrapper); +} + diff --git a/component-definition.json b/component-definition.json index f58cade..69f8ea1 100644 --- a/component-definition.json +++ b/component-definition.json @@ -229,6 +229,22 @@ } } }, + { + "title": "Commerce Teaser", + "id": "commerce-teaser", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Commerce Teaser", + "model": "commerce-teaser", + "filter": "commerce-teaser" + } + } + } + } + }, { "title": "Custom Button", "id": "custom-button", diff --git a/component-filters.json b/component-filters.json index ff802ed..dd6e973 100644 --- a/component-filters.json +++ b/component-filters.json @@ -26,7 +26,8 @@ "video", "gallery", "event-teaser", - "framed-grid" + "framed-grid", + "commerce-teaser" ] }, { diff --git a/component-models.json b/component-models.json index 97e7276..80d4692 100644 --- a/component-models.json +++ b/component-models.json @@ -454,6 +454,71 @@ } ] }, + { + "id": "commerce-teaser", + "fields": [ + { + "component": "text", + "name": "title", + "value": "", + "label": "Product Title" + }, + { + "component": "text", + "name": "price", + "value": "", + "label": "Price", + "description": "Include currency symbol, e.g., $49.99" + }, + { + "component": "richtext", + "name": "description", + "value": "", + "label": "Product Description" + }, + { + "component": "reference", + "name": "image", + "label": "Product Image", + "valueType": "string", + "multi": false + }, + { + "component": "text", + "name": "imageAlt", + "value": "", + "label": "Image Alt Text" + }, + { + "component": "text", + "name": "link", + "value": "", + "label": "Product Link" + }, + { + "component": "text", + "name": "label", + "value": "Shop Now", + "label": "Button Label" + }, + { + "component": "select", + "name": "target", + "value": "_blank", + "label": "Link Target", + "options": [ + { + "name": "New Window (_blank)", + "value": "_blank" + }, + { + "name": "Same Window (_self)", + "value": "" + } + ] + } + ] + }, { "id": "custom-button", "fields": [ diff --git a/models/_section.json b/models/_section.json index 94c06a5..1d2649b 100644 --- a/models/_section.json +++ b/models/_section.json @@ -68,7 +68,8 @@ "video", "gallery", "event-teaser", - "framed-grid" + "framed-grid", + "commerce-teaser" ] } ]