@retrivora-ai/rag-engine 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +34 -15
- package/dist/index.mjs +34 -15
- package/package.json +1 -1
- package/src/components/MessageBubble.tsx +8 -6
- package/src/components/ProductCard.tsx +1 -1
- package/src/components/ProductCarousel.tsx +21 -11
package/dist/index.js
CHANGED
|
@@ -104,7 +104,7 @@ var import_react2 = __toESM(require("react"));
|
|
|
104
104
|
var import_image = __toESM(require("next/image"));
|
|
105
105
|
var import_lucide_react2 = require("lucide-react");
|
|
106
106
|
function ProductCard({ product, primaryColor = "#6366f1" }) {
|
|
107
|
-
return /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-shrink-0 w-
|
|
107
|
+
return /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-shrink-0 w-full bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden" }, product.image ? /* @__PURE__ */ import_react2.default.createElement(
|
|
108
108
|
import_image.default,
|
|
109
109
|
{
|
|
110
110
|
src: product.image,
|
|
@@ -138,25 +138,37 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
|
|
|
138
138
|
const scrollRef = (0, import_react3.useRef)(null);
|
|
139
139
|
const scroll = (direction) => {
|
|
140
140
|
if (scrollRef.current) {
|
|
141
|
-
const
|
|
142
|
-
|
|
141
|
+
const firstCard = scrollRef.current.firstElementChild;
|
|
142
|
+
if (firstCard) {
|
|
143
|
+
const cardWidth = firstCard.offsetWidth + 16;
|
|
144
|
+
const scrollAmount = direction === "left" ? -cardWidth : cardWidth;
|
|
145
|
+
scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
|
|
146
|
+
}
|
|
143
147
|
}
|
|
144
148
|
};
|
|
145
149
|
if (!products || products.length === 0) return null;
|
|
146
|
-
return /* @__PURE__ */ import_react3.default.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -left-
|
|
150
|
+
return /* @__PURE__ */ import_react3.default.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -left-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ import_react3.default.createElement(
|
|
147
151
|
"button",
|
|
148
152
|
{
|
|
149
|
-
onClick: () =>
|
|
150
|
-
|
|
153
|
+
onClick: (e) => {
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
scroll("left");
|
|
156
|
+
},
|
|
157
|
+
className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
|
|
158
|
+
"aria-label": "Scroll left"
|
|
151
159
|
},
|
|
152
|
-
/* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronLeft, { className: "w-
|
|
153
|
-
)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -right-
|
|
160
|
+
/* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronLeft, { className: "w-5 h-5" })
|
|
161
|
+
)), /* @__PURE__ */ import_react3.default.createElement("div", { className: "absolute -right-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ import_react3.default.createElement(
|
|
154
162
|
"button",
|
|
155
163
|
{
|
|
156
|
-
onClick: () =>
|
|
157
|
-
|
|
164
|
+
onClick: (e) => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
scroll("right");
|
|
167
|
+
},
|
|
168
|
+
className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
|
|
169
|
+
"aria-label": "Scroll right"
|
|
158
170
|
},
|
|
159
|
-
/* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronRight, { className: "w-
|
|
171
|
+
/* @__PURE__ */ import_react3.default.createElement(import_lucide_react3.ChevronRight, { className: "w-5 h-5" })
|
|
160
172
|
)), /* @__PURE__ */ import_react3.default.createElement(
|
|
161
173
|
"div",
|
|
162
174
|
{
|
|
@@ -164,7 +176,14 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
|
|
|
164
176
|
className: "flex gap-4 overflow-x-auto pb-4 px-1 scrollbar-hide snap-x snap-mandatory",
|
|
165
177
|
style: { scrollbarWidth: "none", msOverflowStyle: "none" }
|
|
166
178
|
},
|
|
167
|
-
products.map((product) => /* @__PURE__ */ import_react3.default.createElement(
|
|
179
|
+
products.map((product) => /* @__PURE__ */ import_react3.default.createElement(
|
|
180
|
+
"div",
|
|
181
|
+
{
|
|
182
|
+
key: product.id,
|
|
183
|
+
className: "snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
|
|
184
|
+
},
|
|
185
|
+
/* @__PURE__ */ import_react3.default.createElement(ProductCard, { product, primaryColor })
|
|
186
|
+
))
|
|
168
187
|
));
|
|
169
188
|
}
|
|
170
189
|
|
|
@@ -240,14 +259,14 @@ function MessageBubble({
|
|
|
240
259
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
241
260
|
},
|
|
242
261
|
isUser ? /* @__PURE__ */ import_react4.default.createElement(import_lucide_react4.User, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ import_react4.default.createElement(import_lucide_react4.Bot, { className: "w-4 h-4" })
|
|
243
|
-
), /* @__PURE__ */ import_react4.default.createElement("div", { className: `flex flex-col gap-1 max-w-[
|
|
262
|
+
), /* @__PURE__ */ import_react4.default.createElement("div", { className: `flex flex-col gap-1 max-w-[90%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ import_react4.default.createElement(
|
|
244
263
|
"div",
|
|
245
264
|
{
|
|
246
265
|
className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
|
|
247
266
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
248
267
|
},
|
|
249
|
-
isStreaming && !message.content ? /* @__PURE__ */ import_react4.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react4.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react4.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react4.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react4.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, cleanContent || message.content),
|
|
250
|
-
), !isUser && sources && sources.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full" }, /* @__PURE__ */ import_react4.default.createElement(
|
|
268
|
+
isStreaming && !message.content ? /* @__PURE__ */ import_react4.default.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ import_react4.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ import_react4.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ import_react4.default.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ import_react4.default.createElement(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default] }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
269
|
+
), !isUser && hasProducts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ import_react4.default.createElement(ProductCarousel, { products: allProducts, primaryColor })), !isUser && sources && sources.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { className: "w-full" }, /* @__PURE__ */ import_react4.default.createElement(
|
|
251
270
|
"button",
|
|
252
271
|
{
|
|
253
272
|
onClick: () => setShowSources((s) => !s),
|
package/dist/index.mjs
CHANGED
|
@@ -62,7 +62,7 @@ import React2 from "react";
|
|
|
62
62
|
import Image from "next/image";
|
|
63
63
|
import { ShoppingCart, ExternalLink } from "lucide-react";
|
|
64
64
|
function ProductCard({ product, primaryColor = "#6366f1" }) {
|
|
65
|
-
return /* @__PURE__ */ React2.createElement("div", { className: "flex-shrink-0 w-
|
|
65
|
+
return /* @__PURE__ */ React2.createElement("div", { className: "flex-shrink-0 w-full bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group" }, /* @__PURE__ */ React2.createElement("div", { className: "relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden" }, product.image ? /* @__PURE__ */ React2.createElement(
|
|
66
66
|
Image,
|
|
67
67
|
{
|
|
68
68
|
src: product.image,
|
|
@@ -96,25 +96,37 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
|
|
|
96
96
|
const scrollRef = useRef(null);
|
|
97
97
|
const scroll = (direction) => {
|
|
98
98
|
if (scrollRef.current) {
|
|
99
|
-
const
|
|
100
|
-
|
|
99
|
+
const firstCard = scrollRef.current.firstElementChild;
|
|
100
|
+
if (firstCard) {
|
|
101
|
+
const cardWidth = firstCard.offsetWidth + 16;
|
|
102
|
+
const scrollAmount = direction === "left" ? -cardWidth : cardWidth;
|
|
103
|
+
scrollRef.current.scrollBy({ left: scrollAmount, behavior: "smooth" });
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
106
|
};
|
|
103
107
|
if (!products || products.length === 0) return null;
|
|
104
|
-
return /* @__PURE__ */ React3.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ React3.createElement("div", { className: "absolute -left-
|
|
108
|
+
return /* @__PURE__ */ React3.createElement("div", { className: "relative w-full my-4 group/carousel" }, /* @__PURE__ */ React3.createElement("div", { className: "absolute -left-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ React3.createElement(
|
|
105
109
|
"button",
|
|
106
110
|
{
|
|
107
|
-
onClick: () =>
|
|
108
|
-
|
|
111
|
+
onClick: (e) => {
|
|
112
|
+
e.stopPropagation();
|
|
113
|
+
scroll("left");
|
|
114
|
+
},
|
|
115
|
+
className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
|
|
116
|
+
"aria-label": "Scroll left"
|
|
109
117
|
},
|
|
110
|
-
/* @__PURE__ */ React3.createElement(ChevronLeft, { className: "w-
|
|
111
|
-
)), /* @__PURE__ */ React3.createElement("div", { className: "absolute -right-
|
|
118
|
+
/* @__PURE__ */ React3.createElement(ChevronLeft, { className: "w-5 h-5" })
|
|
119
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "absolute -right-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity" }, /* @__PURE__ */ React3.createElement(
|
|
112
120
|
"button",
|
|
113
121
|
{
|
|
114
|
-
onClick: () =>
|
|
115
|
-
|
|
122
|
+
onClick: (e) => {
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
scroll("right");
|
|
125
|
+
},
|
|
126
|
+
className: "p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95",
|
|
127
|
+
"aria-label": "Scroll right"
|
|
116
128
|
},
|
|
117
|
-
/* @__PURE__ */ React3.createElement(ChevronRight, { className: "w-
|
|
129
|
+
/* @__PURE__ */ React3.createElement(ChevronRight, { className: "w-5 h-5" })
|
|
118
130
|
)), /* @__PURE__ */ React3.createElement(
|
|
119
131
|
"div",
|
|
120
132
|
{
|
|
@@ -122,7 +134,14 @@ function ProductCarousel({ products, primaryColor = "#6366f1" }) {
|
|
|
122
134
|
className: "flex gap-4 overflow-x-auto pb-4 px-1 scrollbar-hide snap-x snap-mandatory",
|
|
123
135
|
style: { scrollbarWidth: "none", msOverflowStyle: "none" }
|
|
124
136
|
},
|
|
125
|
-
products.map((product) => /* @__PURE__ */ React3.createElement(
|
|
137
|
+
products.map((product) => /* @__PURE__ */ React3.createElement(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
key: product.id,
|
|
141
|
+
className: "snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
|
|
142
|
+
},
|
|
143
|
+
/* @__PURE__ */ React3.createElement(ProductCard, { product, primaryColor })
|
|
144
|
+
))
|
|
126
145
|
));
|
|
127
146
|
}
|
|
128
147
|
|
|
@@ -198,14 +217,14 @@ function MessageBubble({
|
|
|
198
217
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
199
218
|
},
|
|
200
219
|
isUser ? /* @__PURE__ */ React4.createElement(User, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ React4.createElement(Bot, { className: "w-4 h-4" })
|
|
201
|
-
), /* @__PURE__ */ React4.createElement("div", { className: `flex flex-col gap-1 max-w-[
|
|
220
|
+
), /* @__PURE__ */ React4.createElement("div", { className: `flex flex-col gap-1 max-w-[90%] ${isUser ? "items-end" : "items-start"}` }, /* @__PURE__ */ React4.createElement(
|
|
202
221
|
"div",
|
|
203
222
|
{
|
|
204
223
|
className: `relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${isUser ? "text-white rounded-tr-sm" : "bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 text-slate-800 dark:text-white/90 rounded-tl-sm"}`,
|
|
205
224
|
style: isUser ? { background: `linear-gradient(135deg, ${primaryColor}, ${accentColor})` } : {}
|
|
206
225
|
},
|
|
207
|
-
isStreaming && !message.content ? /* @__PURE__ */ React4.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React4.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React4.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, cleanContent || message.content),
|
|
208
|
-
), !isUser && sources && sources.length > 0 && /* @__PURE__ */ React4.createElement("div", { className: "w-full" }, /* @__PURE__ */ React4.createElement(
|
|
226
|
+
isStreaming && !message.content ? /* @__PURE__ */ React4.createElement("span", { className: "flex items-center gap-1 py-1" }, /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:0ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:150ms]" }), /* @__PURE__ */ React4.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-slate-400 dark:bg-white/60 animate-bounce [animation-delay:300ms]" })) : /* @__PURE__ */ React4.createElement("div", { className: `prose prose-sm max-w-none ${isUser ? "prose-invert" : "dark:prose-invert"}` }, /* @__PURE__ */ React4.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, cleanContent || message.content), isStreaming && message.content && /* @__PURE__ */ React4.createElement("span", { className: "inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" }))
|
|
227
|
+
), !isUser && hasProducts && /* @__PURE__ */ React4.createElement("div", { className: "w-full mt-1" }, /* @__PURE__ */ React4.createElement(ProductCarousel, { products: allProducts, primaryColor })), !isUser && sources && sources.length > 0 && /* @__PURE__ */ React4.createElement("div", { className: "w-full" }, /* @__PURE__ */ React4.createElement(
|
|
209
228
|
"button",
|
|
210
229
|
{
|
|
211
230
|
onClick: () => setShowSources((s) => !s),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
|
@@ -118,7 +118,7 @@ export function MessageBubble({
|
|
|
118
118
|
}
|
|
119
119
|
</div>
|
|
120
120
|
|
|
121
|
-
<div className={`flex flex-col gap-1 max-w-[
|
|
121
|
+
<div className={`flex flex-col gap-1 max-w-[90%] ${isUser ? 'items-end' : 'items-start'}`}>
|
|
122
122
|
{/* Bubble */}
|
|
123
123
|
<div
|
|
124
124
|
className={`relative px-4 py-3 rounded-2xl text-sm leading-relaxed shadow-sm dark:shadow-lg ${
|
|
@@ -144,11 +144,6 @@ export function MessageBubble({
|
|
|
144
144
|
{cleanContent || message.content}
|
|
145
145
|
</ReactMarkdown>
|
|
146
146
|
|
|
147
|
-
{/* Product Carousel */}
|
|
148
|
-
{hasProducts && (
|
|
149
|
-
<ProductCarousel products={allProducts} primaryColor={primaryColor} />
|
|
150
|
-
)}
|
|
151
|
-
|
|
152
147
|
{isStreaming && message.content && (
|
|
153
148
|
<span className="inline-block w-1.5 h-3.5 ml-1 bg-emerald-500 animate-pulse align-middle" />
|
|
154
149
|
)}
|
|
@@ -156,6 +151,13 @@ export function MessageBubble({
|
|
|
156
151
|
)}
|
|
157
152
|
</div>
|
|
158
153
|
|
|
154
|
+
{/* Product Carousel (Outside the bubble) */}
|
|
155
|
+
{!isUser && hasProducts && (
|
|
156
|
+
<div className="w-full mt-1">
|
|
157
|
+
<ProductCarousel products={allProducts} primaryColor={primaryColor} />
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
|
|
159
161
|
{/* Sources toggle */}
|
|
160
162
|
{!isUser && sources && sources.length > 0 && (
|
|
161
163
|
<div className="w-full">
|
|
@@ -21,7 +21,7 @@ interface ProductCardProps {
|
|
|
21
21
|
|
|
22
22
|
export function ProductCard({ product, primaryColor = '#6366f1' }: ProductCardProps) {
|
|
23
23
|
return (
|
|
24
|
-
<div className="flex-shrink-0 w-
|
|
24
|
+
<div className="flex-shrink-0 w-full bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 group">
|
|
25
25
|
{/* Product Image */}
|
|
26
26
|
<div className="relative h-40 w-full bg-slate-100 dark:bg-white/5 flex items-center justify-center overflow-hidden">
|
|
27
27
|
{product.image ? (
|
|
@@ -14,8 +14,13 @@ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductC
|
|
|
14
14
|
|
|
15
15
|
const scroll = (direction: 'left' | 'right') => {
|
|
16
16
|
if (scrollRef.current) {
|
|
17
|
-
const
|
|
18
|
-
|
|
17
|
+
const firstCard = scrollRef.current.firstElementChild as HTMLElement;
|
|
18
|
+
if (firstCard) {
|
|
19
|
+
// Use actual rendered width + gap
|
|
20
|
+
const cardWidth = firstCard.offsetWidth + 16;
|
|
21
|
+
const scrollAmount = direction === 'left' ? -cardWidth : cardWidth;
|
|
22
|
+
scrollRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' });
|
|
23
|
+
}
|
|
19
24
|
}
|
|
20
25
|
};
|
|
21
26
|
|
|
@@ -24,21 +29,23 @@ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductC
|
|
|
24
29
|
return (
|
|
25
30
|
<div className="relative w-full my-4 group/carousel">
|
|
26
31
|
{/* Scroll Buttons */}
|
|
27
|
-
<div className="absolute -left-
|
|
32
|
+
<div className="absolute -left-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity">
|
|
28
33
|
<button
|
|
29
|
-
onClick={() => scroll('left')}
|
|
30
|
-
className="p-
|
|
34
|
+
onClick={(e) => { e.stopPropagation(); scroll('left'); }}
|
|
35
|
+
className="p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95"
|
|
36
|
+
aria-label="Scroll left"
|
|
31
37
|
>
|
|
32
|
-
<ChevronLeft className="w-
|
|
38
|
+
<ChevronLeft className="w-5 h-5" />
|
|
33
39
|
</button>
|
|
34
40
|
</div>
|
|
35
41
|
|
|
36
|
-
<div className="absolute -right-
|
|
42
|
+
<div className="absolute -right-4 top-1/2 -translate-y-1/2 z-20 opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 transition-opacity">
|
|
37
43
|
<button
|
|
38
|
-
onClick={() => scroll('right')}
|
|
39
|
-
className="p-
|
|
44
|
+
onClick={(e) => { e.stopPropagation(); scroll('right'); }}
|
|
45
|
+
className="p-2 rounded-full bg-white dark:bg-slate-800 shadow-xl border border-slate-200 dark:border-white/10 text-slate-600 dark:text-white/80 hover:text-indigo-500 dark:hover:text-indigo-400 transition-all hover:scale-110 active:scale-95"
|
|
46
|
+
aria-label="Scroll right"
|
|
40
47
|
>
|
|
41
|
-
<ChevronRight className="w-
|
|
48
|
+
<ChevronRight className="w-5 h-5" />
|
|
42
49
|
</button>
|
|
43
50
|
</div>
|
|
44
51
|
|
|
@@ -49,7 +56,10 @@ export function ProductCarousel({ products, primaryColor = '#6366f1' }: ProductC
|
|
|
49
56
|
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
|
50
57
|
>
|
|
51
58
|
{products.map((product) => (
|
|
52
|
-
<div
|
|
59
|
+
<div
|
|
60
|
+
key={product.id}
|
|
61
|
+
className="snap-start flex-shrink-0 w-[85%] sm:w-[48%] md:w-[31%] min-w-[200px] max-w-[300px]"
|
|
62
|
+
>
|
|
53
63
|
<ProductCard product={product} primaryColor={primaryColor} />
|
|
54
64
|
</div>
|
|
55
65
|
))}
|