ecom-widget
Version:
Embeddable 5-item infinite carousel widget
129 lines (116 loc) • 5.13 kB
JavaScript
(function(){"use strict";window.EcomWidget={init(c){const b=c.selector||"#my-ecom-widget",p=document.querySelector(b);if(!p)return console.warn("Container not found");const n=c.visibleCount||5,l=500,f=16,s=p.attachShadow({mode:"open"});s.innerHTML=`
<style>
:host { all: initial; }
.widget-wrapper { position: relative; }
.widget-box {
--visible: ${n};
--gap: ${f}px;
font-family: 'Inter', sans-serif;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
overflow: hidden;
position: relative;
}
/* Edge fade overlays */
.widget-box::before,
.widget-box::after {
content: "";
position: absolute;
top: 0; bottom: 0;
width: calc((100% - (100% - (var(--visible) - 1) * var(--gap))) / 2);
pointer-events: none;
z-index: 5;
}
.widget-box::before {
left: 0;
background: linear-gradient(to right, #fff, transparent);
}
.widget-box::after {
right: 0;
background: linear-gradient(to left, #fff, transparent);
}
#products {
display: flex;
gap: var(--gap);
transition: transform ${l}ms ease;
will-change: transform;
}
.product {
flex: 0 0 calc((100% - (var(--visible) - 1) * var(--gap)) / var(--visible));
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
padding: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.product img {
width: 80px;
height: 80px;
object-fit: contain;
margin-bottom: 8px;
}
.info {
text-align: center;
}
.info h4 {
font-size: 14px;
margin: 0 0 4px;
color: #111;
height: 32px;
overflow: hidden;
}
.info p {
font-size: 13px;
margin: 0;
color: #666;
}
.action-btn {
margin-top: 8px;
padding: 6px 12px;
font-size: 13px;
border: none;
border-radius: 4px;
background: #0070f3;
color: #fff;
cursor: pointer;
transition: background 0.2s;
}
.action-btn:hover { background: #0055aa; }
.action-btn:disabled { background: #ccc; cursor: default; }
.nav-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 32px; height: 32px;
border: none;
background: rgba(255,255,255,0.9);
border-radius: 50%;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
cursor: pointer;
font-size: 16px;
line-height: 32px;
text-align: center;
padding: 0;
z-index: 10;
}
.prev { left: 8px; }
.next { right: 8px; }
</style>
<div class="widget-wrapper">
<button class="nav-button prev">❮</button>
<div class="widget-box"><div id="products">Loading…</div></div>
<button class="nav-button next">❯</button>
</div>
`;const t=s.getElementById("products"),x=s.querySelector(".prev"),u=s.querySelector(".next");let e;fetch(`https://fakestoreapi.com/products/category/${c.category||"electronics"}`).then(o=>o.json()).then(o=>{const g=o.length,h=o.slice(0,n),v=[...o.slice(-n),...o,...h];t.innerHTML="",v.forEach(a=>{const d=document.createElement("div");d.className="product",d.innerHTML=`
<img src="${a.image}" alt="${a.title}" />
<div class="info">
<h4>${a.title}</h4>
<p>$${a.price}</p>
</div>
<button class="action-btn">Add</button>
`;const r=d.querySelector("button");r.addEventListener("click",()=>{r.disabled=!0,r.textContent="Adding…",fetch("https://fakestoreapi.com/carts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userId:1,date:new Date().toISOString(),products:[{productId:a.id,quantity:1}]})}).then(()=>r.textContent="Added!").catch(()=>{r.textContent="Error",r.disabled=!1})}),t.appendChild(d)});const i=t.children[0].getBoundingClientRect().width+f;e=n,t.style.transform=`translateX(-${i*e}px)`,t.addEventListener("transitionend",()=>{t.style.transition="",e>=g+n&&(e=n,t.style.transition="none",t.style.transform=`translateX(-${i*e}px)`),e<n&&(e=g+n-1,t.style.transition="none",t.style.transform=`translateX(-${i*e}px)`)}),x.addEventListener("click",()=>{t.style.transition=`transform ${l}ms ease`,e--,t.style.transform=`translateX(-${i*e}px)`}),u.addEventListener("click",()=>{t.style.transition=`transform ${l}ms ease`,e++,t.style.transform=`translateX(-${i*e}px)`})}).catch(()=>{t.innerHTML='<p style="padding:16px;color:#900">Error loading products.</p>'})}}})();