Skip to content

Commit 945f75f

Browse files
author
GitHub Actions Bot
committed
feat(AEMCP-100): Apply AI-generated changes [skip ci]
1 parent 9dea18b commit 945f75f

7 files changed

Lines changed: 402 additions & 2 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"definitions": [
3+
{
4+
"title": "Commerce Teaser",
5+
"id": "commerce-teaser",
6+
"plugins": {
7+
"xwalk": {
8+
"page": {
9+
"resourceType": "core/franklin/components/block/v1/block",
10+
"template": {
11+
"name": "Commerce Teaser",
12+
"model": "commerce-teaser",
13+
"filter": "commerce-teaser"
14+
}
15+
}
16+
}
17+
}
18+
}
19+
],
20+
"models": [
21+
{
22+
"id": "commerce-teaser",
23+
"fields": [
24+
{
25+
"component": "text",
26+
"name": "title",
27+
"value": "",
28+
"label": "Product Title"
29+
},
30+
{
31+
"component": "text",
32+
"name": "price",
33+
"value": "",
34+
"label": "Price",
35+
"description": "Include currency symbol, e.g., $49.99"
36+
},
37+
{
38+
"component": "richtext",
39+
"name": "description",
40+
"value": "",
41+
"label": "Product Description"
42+
},
43+
{
44+
"component": "reference",
45+
"name": "image",
46+
"label": "Product Image",
47+
"valueType": "string",
48+
"multi": false
49+
},
50+
{
51+
"component": "text",
52+
"name": "imageAlt",
53+
"value": "",
54+
"label": "Image Alt Text"
55+
},
56+
{
57+
"component": "text",
58+
"name": "link",
59+
"value": "",
60+
"label": "Product Link"
61+
},
62+
{
63+
"component": "text",
64+
"name": "label",
65+
"value": "Shop Now",
66+
"label": "Button Label"
67+
},
68+
{
69+
"component": "select",
70+
"name": "target",
71+
"value": "_blank",
72+
"label": "Link Target",
73+
"options": [
74+
{
75+
"name": "New Window (_blank)",
76+
"value": "_blank"
77+
},
78+
{
79+
"name": "Same Window (_self)",
80+
"value": ""
81+
}
82+
]
83+
}
84+
]
85+
}
86+
],
87+
"filters": []
88+
}
89+
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* Commerce Teaser Block Styles */
2+
3+
.commerce-teaser {
4+
display: flex;
5+
flex-direction: column;
6+
border: 1px solid rgba(var(--text-color), 0.1);
7+
border-radius: 8px;
8+
overflow: hidden;
9+
background: rgb(var(--background-color));
10+
transition: transform 0.2s ease, box-shadow 0.2s ease;
11+
}
12+
13+
.commerce-teaser:hover {
14+
transform: translateY(-3px);
15+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
16+
}
17+
18+
/* Image */
19+
.commerce-teaser-image {
20+
position: relative;
21+
aspect-ratio: 4 / 3;
22+
width: 100%;
23+
overflow: hidden;
24+
}
25+
26+
.commerce-teaser-image picture,
27+
.commerce-teaser-image img {
28+
width: 100%;
29+
height: 100%;
30+
object-fit: cover;
31+
display: block;
32+
}
33+
34+
/* Content */
35+
.commerce-teaser-content {
36+
padding: var(--spacing-m);
37+
display: flex;
38+
flex-direction: column;
39+
gap: var(--spacing-s);
40+
}
41+
42+
.commerce-teaser-title {
43+
font-size: var(--heading-font-size-m);
44+
font-weight: var(--font-bold);
45+
margin: 0;
46+
}
47+
48+
.commerce-teaser-price {
49+
font-size: calc(var(--heading-font-size-s) * 1.1);
50+
font-weight: var(--font-bold);
51+
color: rgb(var(--button-primary));
52+
margin: 0;
53+
}
54+
55+
.commerce-teaser-description {
56+
font-size: var(--body-font-size-s);
57+
margin: 0;
58+
overflow: hidden;
59+
text-overflow: ellipsis;
60+
display: -webkit-box;
61+
-webkit-line-clamp: 3; /* show up to 3 lines */
62+
-webkit-box-orient: vertical;
63+
}
64+
65+
.commerce-teaser-button-container {
66+
margin-top: auto;
67+
}
68+
69+
.commerce-teaser-button-container .button {
70+
display: inline-block;
71+
padding: var(--btn-padding);
72+
background: rgb(var(--button-primary));
73+
color: rgb(var(--on-button-primary));
74+
border-radius: 4px;
75+
text-decoration: none;
76+
font-weight: var(--font-bold);
77+
transition: background 0.3s ease, color 0.3s ease;
78+
}
79+
80+
.commerce-teaser-button-container .button:hover,
81+
.commerce-teaser-button-container .button:focus {
82+
background: rgb(var(--button-primary-hover));
83+
color: rgb(var(--on-button-primary));
84+
}
85+
86+
/* Responsive */
87+
@media (width >= 768px) {
88+
.commerce-teaser {
89+
flex-direction: row;
90+
max-height: 320px;
91+
}
92+
93+
.commerce-teaser-image {
94+
flex: 0 0 40%;
95+
aspect-ratio: unset;
96+
height: 100%;
97+
}
98+
99+
.commerce-teaser-content {
100+
flex: 1;
101+
}
102+
}
103+
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { setBlockItemOptions, moveClassToTargetedChild } from '../../scripts/utils.js';
2+
import { createOptimizedPicture } from '../../scripts/aem.js';
3+
import { moveInstrumentation } from '../../scripts/scripts.js';
4+
import { renderButton } from '../../components/button/button.js';
5+
6+
/*
7+
* Commerce-Teaser Block
8+
*
9+
* Authoring column order:
10+
* 0 — Product Title (text)
11+
* 1 — Price (text, supports currency formatting)
12+
* 2 — Description (rich-text / text)
13+
* 3 — Product Image (asset reference or <picture>)
14+
* 4 — Image Alt Text (text – optional)
15+
* 5 — Product Link (URL)
16+
* 6 — Button Label (text – optional, defaults to "Shop Now")
17+
* 7 — Link Target (select – optional, _blank opens new window)
18+
*/
19+
20+
export default function decorate(block) {
21+
/* ---------------- Parse configuration ---------------- */
22+
const blockItemsOptions = [];
23+
const blockItemMap = [
24+
{ name: 'title' },
25+
{ name: 'price' },
26+
{ name: 'description' },
27+
{ name: 'image' },
28+
{ name: 'imageAlt' },
29+
{ name: 'link' },
30+
{ name: 'label' },
31+
{ name: 'target' },
32+
];
33+
34+
setBlockItemOptions(block, blockItemMap, blockItemsOptions);
35+
const config = blockItemsOptions[0] || {};
36+
37+
/* ---------------- Build DOM ---------------- */
38+
const wrapper = document.createElement('div');
39+
wrapper.className = 'commerce-teaser';
40+
41+
/* Image */
42+
const imageContainer = document.createElement('div');
43+
imageContainer.className = 'commerce-teaser-image';
44+
45+
if (config.image) {
46+
let optimizedPicture;
47+
try {
48+
optimizedPicture = createOptimizedPicture(
49+
config.image,
50+
config.imageAlt || config.title || 'Product image',
51+
true,
52+
[{ width: '750' }],
53+
);
54+
} catch (e) {
55+
// When config.image is already a <picture>
56+
optimizedPicture = config.image.cloneNode(true);
57+
}
58+
imageContainer.appendChild(optimizedPicture);
59+
} else {
60+
const placeholder = createOptimizedPicture(
61+
'https://placehold.co/600x450?text=No+Image',
62+
config.imageAlt || 'Placeholder product image',
63+
true,
64+
[{ width: '750' }],
65+
);
66+
imageContainer.appendChild(placeholder);
67+
}
68+
69+
/* Content */
70+
const contentContainer = document.createElement('div');
71+
contentContainer.className = 'commerce-teaser-content';
72+
73+
if (config.title) {
74+
const titleEl = document.createElement('h3');
75+
titleEl.className = 'commerce-teaser-title';
76+
titleEl.textContent = config.title;
77+
contentContainer.appendChild(titleEl);
78+
}
79+
80+
if (config.price) {
81+
const priceEl = document.createElement('p');
82+
priceEl.className = 'commerce-teaser-price';
83+
priceEl.textContent = config.price;
84+
priceEl.setAttribute('aria-label', `Price: ${config.price}`);
85+
contentContainer.appendChild(priceEl);
86+
}
87+
88+
if (config.description) {
89+
const descEl = document.createElement('p');
90+
descEl.className = 'commerce-teaser-description';
91+
descEl.textContent = config.description;
92+
contentContainer.appendChild(descEl);
93+
}
94+
95+
if (config.link) {
96+
const buttonContainer = document.createElement('div');
97+
buttonContainer.className = 'commerce-teaser-button-container';
98+
99+
const anchor = document.createElement('a');
100+
anchor.href = config.link;
101+
102+
const button = renderButton({
103+
linkButton: anchor,
104+
linkText: config.label || 'Shop Now',
105+
linkTitle: config.title || 'View Product',
106+
linkTarget: config.target || '_blank',
107+
linkType: '',
108+
linkStyle: '',
109+
});
110+
111+
buttonContainer.appendChild(button);
112+
moveClassToTargetedChild(block, button);
113+
contentContainer.appendChild(buttonContainer);
114+
}
115+
116+
/* Assemble */
117+
wrapper.appendChild(imageContainer);
118+
wrapper.appendChild(contentContainer);
119+
120+
moveInstrumentation(block, wrapper);
121+
122+
block.textContent = '';
123+
block.appendChild(wrapper);
124+
}
125+

component-definition.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,22 @@
229229
}
230230
}
231231
},
232+
{
233+
"title": "Commerce Teaser",
234+
"id": "commerce-teaser",
235+
"plugins": {
236+
"xwalk": {
237+
"page": {
238+
"resourceType": "core/franklin/components/block/v1/block",
239+
"template": {
240+
"name": "Commerce Teaser",
241+
"model": "commerce-teaser",
242+
"filter": "commerce-teaser"
243+
}
244+
}
245+
}
246+
}
247+
},
232248
{
233249
"title": "Custom Button",
234250
"id": "custom-button",

component-filters.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"video",
2727
"gallery",
2828
"event-teaser",
29-
"framed-grid"
29+
"framed-grid",
30+
"commerce-teaser"
3031
]
3132
},
3233
{

0 commit comments

Comments
 (0)