@xenterprises/nuxt-x-marketing 1.2.2 → 1.4.0

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.
@@ -1,431 +0,0 @@
1
- <template>
2
- <!-- Card Layout (vertical) -->
3
- <article
4
- v-if="layout === 'card'"
5
- class="group relative flex flex-col rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 overflow-hidden transition-all duration-300 ease-out hover:-translate-y-1 hover:shadow-[0_12px_40px_rgba(128,128,128,0.15)] dark:hover:shadow-[0_12px_40px_rgba(255,255,255,0.07)]"
6
- >
7
- <!-- Rank Badge -->
8
- <div v-if="product.badge" class="absolute top-3 left-3 z-10">
9
- <UBadge
10
- :label="product.badge"
11
- color="primary"
12
- variant="solid"
13
- size="sm"
14
- />
15
- </div>
16
-
17
- <!-- Product Image -->
18
- <figure
19
- class="aspect-4/3 overflow-hidden bg-neutral-50 dark:bg-neutral-800"
20
- >
21
- <img
22
- v-if="product.image"
23
- :src="product.image"
24
- :alt="product.name"
25
- class="w-full h-full object-contain p-4 transition-transform duration-300 group-hover:scale-105"
26
- />
27
- <div v-else class="w-full h-full flex items-center justify-center">
28
- <UIcon
29
- name="i-lucide-package"
30
- class="w-16 h-16 text-neutral-300 dark:text-neutral-600"
31
- />
32
- </div>
33
- </figure>
34
-
35
- <!-- Content -->
36
- <div class="flex flex-col flex-1 p-5">
37
- <!-- Name -->
38
- <h3
39
- class="font-semibold text-neutral-900 dark:text-white text-base leading-snug mb-2"
40
- >
41
- {{ product.name }}
42
- </h3>
43
-
44
- <!-- Rating -->
45
- <div
46
- v-if="showRating && product.rating"
47
- class="flex items-center gap-2 mb-3"
48
- >
49
- <div class="flex items-center gap-0.5">
50
- <UIcon
51
- v-for="i in 5"
52
- :key="i"
53
- name="i-lucide-star"
54
- class="w-3.5 h-3.5"
55
- :class="
56
- i <= Math.round(product.rating)
57
- ? 'text-yellow-400'
58
- : 'text-neutral-300 dark:text-neutral-600'
59
- "
60
- />
61
- </div>
62
- <span
63
- class="text-sm font-medium text-neutral-700 dark:text-neutral-300"
64
- >
65
- {{ product.rating.toFixed(1) }}
66
- </span>
67
- <span
68
- v-if="product.reviewCount"
69
- class="text-xs text-neutral-500 dark:text-neutral-400"
70
- >
71
- ({{ product.reviewCount.toLocaleString() }})
72
- </span>
73
- </div>
74
-
75
- <!-- Price -->
76
- <div v-if="showPrice" class="flex items-baseline gap-2 mb-4">
77
- <span class="text-xl font-bold text-neutral-900 dark:text-white">
78
- {{ product.price }}
79
- </span>
80
- <span
81
- v-if="product.originalPrice"
82
- class="text-sm text-neutral-400 dark:text-neutral-500 line-through"
83
- >
84
- {{ product.originalPrice }}
85
- </span>
86
- </div>
87
-
88
- <!-- Pros -->
89
- <ul v-if="showPros && product.pros?.length" class="space-y-1.5 mb-4">
90
- <li
91
- v-for="pro in product.pros"
92
- :key="pro"
93
- class="flex items-start gap-2 text-sm text-neutral-700 dark:text-neutral-300"
94
- >
95
- <UIcon
96
- name="i-lucide-check"
97
- class="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5"
98
- />
99
- <span>{{ pro }}</span>
100
- </li>
101
- </ul>
102
-
103
- <!-- Cons -->
104
- <ul v-if="showCons && product.cons?.length" class="space-y-1.5 mb-4">
105
- <li
106
- v-for="con in product.cons"
107
- :key="con"
108
- class="flex items-start gap-2 text-sm text-neutral-700 dark:text-neutral-300"
109
- >
110
- <UIcon
111
- name="i-lucide-x"
112
- class="w-4 h-4 text-red-500 flex-shrink-0 mt-0.5"
113
- />
114
- <span>{{ con }}</span>
115
- </li>
116
- </ul>
117
-
118
- <!-- Spacer -->
119
- <div class="flex-1" />
120
-
121
- <!-- CTA Button -->
122
- <a
123
- :href="affiliateHref"
124
- target="_blank"
125
- rel="noopener noreferrer"
126
- class="mt-auto"
127
- >
128
- <UButton
129
- :label="buttonLabel"
130
- :trailing-icon="buttonIcon"
131
- color="primary"
132
- variant="solid"
133
- size="md"
134
- block
135
- class="justify-center"
136
- />
137
- </a>
138
-
139
- <!-- Disclosure -->
140
- <p
141
- v-if="disclosure"
142
- class="mt-2 text-xs text-neutral-400 dark:text-neutral-500 text-center italic"
143
- >
144
- {{ disclosure }}
145
- </p>
146
- </div>
147
- </article>
148
-
149
- <!-- Row Layout (horizontal) -->
150
- <article
151
- v-else-if="layout === 'row'"
152
- class="group relative flex gap-4 p-4 rounded-xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 transition-all duration-300 ease-out hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(128,128,128,0.12)] dark:hover:shadow-[0_8px_24px_rgba(255,255,255,0.06)]"
153
- >
154
- <!-- Rank Badge -->
155
- <div v-if="product.badge" class="absolute top-3 right-3 z-10">
156
- <UBadge
157
- :label="product.badge"
158
- color="primary"
159
- variant="solid"
160
- size="xs"
161
- />
162
- </div>
163
-
164
- <!-- Product Image -->
165
- <figure
166
- class="w-32 h-32 flex-shrink-0 rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-800 border border-neutral-100 dark:border-neutral-700"
167
- >
168
- <img
169
- v-if="product.image"
170
- :src="product.image"
171
- :alt="product.name"
172
- class="w-full h-full object-contain p-2"
173
- />
174
- <div v-else class="w-full h-full flex items-center justify-center">
175
- <UIcon
176
- name="i-lucide-package"
177
- class="w-10 h-10 text-neutral-300 dark:text-neutral-600"
178
- />
179
- </div>
180
- </figure>
181
-
182
- <!-- Content -->
183
- <div class="flex flex-col flex-1 min-w-0">
184
- <h3
185
- class="font-semibold text-neutral-900 dark:text-white text-base leading-snug mb-1.5"
186
- >
187
- {{ product.name }}
188
- </h3>
189
-
190
- <!-- Rating -->
191
- <div
192
- v-if="showRating && product.rating"
193
- class="flex items-center gap-1.5 mb-2"
194
- >
195
- <div class="flex items-center gap-0.5">
196
- <UIcon
197
- v-for="i in 5"
198
- :key="i"
199
- name="i-lucide-star"
200
- class="w-3 h-3"
201
- :class="
202
- i <= Math.round(product.rating)
203
- ? 'text-yellow-400'
204
- : 'text-neutral-300 dark:text-neutral-600'
205
- "
206
- />
207
- </div>
208
- <span class="text-xs text-neutral-500 dark:text-neutral-400">
209
- {{ product.rating.toFixed(1) }}
210
- <span v-if="product.reviewCount"
211
- >({{ product.reviewCount.toLocaleString() }})</span
212
- >
213
- </span>
214
- </div>
215
-
216
- <!-- Price -->
217
- <div v-if="showPrice" class="flex items-baseline gap-2 mb-3">
218
- <span class="text-lg font-bold text-neutral-900 dark:text-white">
219
- {{ product.price }}
220
- </span>
221
- <span
222
- v-if="product.originalPrice"
223
- class="text-sm text-neutral-400 dark:text-neutral-500 line-through"
224
- >
225
- {{ product.originalPrice }}
226
- </span>
227
- </div>
228
-
229
- <!-- Pros -->
230
- <ul v-if="showPros && product.pros?.length" class="space-y-1 mb-3">
231
- <li
232
- v-for="pro in product.pros.slice(0, 3)"
233
- :key="pro"
234
- class="flex items-start gap-1.5 text-sm text-neutral-700 dark:text-neutral-300"
235
- >
236
- <UIcon
237
- name="i-lucide-check"
238
- class="w-3.5 h-3.5 text-green-500 flex-shrink-0 mt-0.5"
239
- />
240
- <span>{{ pro }}</span>
241
- </li>
242
- </ul>
243
-
244
- <!-- Cons -->
245
- <ul v-if="showCons && product.cons?.length" class="space-y-1 mb-3">
246
- <li
247
- v-for="con in product.cons.slice(0, 2)"
248
- :key="con"
249
- class="flex items-start gap-1.5 text-sm text-neutral-700 dark:text-neutral-300"
250
- >
251
- <UIcon
252
- name="i-lucide-x"
253
- class="w-3.5 h-3.5 text-red-500 flex-shrink-0 mt-0.5"
254
- />
255
- <span>{{ con }}</span>
256
- </li>
257
- </ul>
258
-
259
- <!-- CTA Button -->
260
- <a
261
- :href="affiliateHref"
262
- target="_blank"
263
- rel="noopener noreferrer"
264
- class="mt-auto"
265
- >
266
- <UButton
267
- :label="buttonLabel"
268
- :trailing-icon="buttonIcon"
269
- color="primary"
270
- variant="solid"
271
- size="sm"
272
- />
273
- </a>
274
-
275
- <!-- Disclosure -->
276
- <p
277
- v-if="disclosure"
278
- class="mt-1.5 text-xs text-neutral-400 dark:text-neutral-500 italic"
279
- >
280
- {{ disclosure }}
281
- </p>
282
- </div>
283
- </article>
284
-
285
- <!-- Compact Layout (minimal one-row) -->
286
- <article
287
- v-else-if="layout === 'compact'"
288
- class="group flex items-center gap-3 py-3 px-3 rounded-lg border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 transition-all duration-200 hover:bg-neutral-50 dark:hover:bg-neutral-800/50"
289
- >
290
- <!-- Product Image -->
291
- <figure
292
- class="w-16 h-16 flex-shrink-0 rounded-lg overflow-hidden bg-neutral-50 dark:bg-neutral-800 border border-neutral-100 dark:border-neutral-700"
293
- >
294
- <img
295
- v-if="product.image"
296
- :src="product.image"
297
- :alt="product.name"
298
- class="w-full h-full object-contain p-1"
299
- />
300
- <div v-else class="w-full h-full flex items-center justify-center">
301
- <UIcon
302
- name="i-lucide-package"
303
- class="w-7 h-7 text-neutral-300 dark:text-neutral-600"
304
- />
305
- </div>
306
- </figure>
307
-
308
- <!-- Name + Rating -->
309
- <div class="flex-1 min-w-0">
310
- <div class="flex items-center gap-2 flex-wrap">
311
- <h3
312
- class="font-medium text-sm text-neutral-900 dark:text-white truncate"
313
- >
314
- {{ product.name }}
315
- </h3>
316
- <UBadge
317
- v-if="product.badge"
318
- :label="product.badge"
319
- color="primary"
320
- variant="subtle"
321
- size="xs"
322
- />
323
- </div>
324
- <div
325
- v-if="showRating && product.rating"
326
- class="flex items-center gap-1 mt-0.5"
327
- >
328
- <UIcon name="i-lucide-star" class="w-3 h-3 text-yellow-400" />
329
- <span class="text-xs text-neutral-500 dark:text-neutral-400">{{
330
- product.rating.toFixed(1)
331
- }}</span>
332
- </div>
333
- </div>
334
-
335
- <!-- Price -->
336
- <div v-if="showPrice" class="flex-shrink-0 text-right">
337
- <div class="font-bold text-sm text-neutral-900 dark:text-white">
338
- {{ product.price }}
339
- </div>
340
- <div
341
- v-if="product.originalPrice"
342
- class="text-xs text-neutral-400 line-through"
343
- >
344
- {{ product.originalPrice }}
345
- </div>
346
- </div>
347
-
348
- <!-- CTA Button -->
349
- <a
350
- :href="affiliateHref"
351
- target="_blank"
352
- rel="noopener noreferrer"
353
- class="flex-shrink-0"
354
- >
355
- <UButton
356
- :label="buttonLabel"
357
- :trailing-icon="buttonIcon"
358
- color="primary"
359
- variant="solid"
360
- size="xs"
361
- />
362
- </a>
363
- </article>
364
- </template>
365
-
366
- <script setup>
367
- const props = defineProps({
368
- /** Product data object */
369
- product: {
370
- type: Object,
371
- default: () => ({
372
- name: "Product Name",
373
- price: "$99.99",
374
- rating: 4.5,
375
- reviewCount: 1200,
376
- pros: ["Great quality", "Easy to use", "Good value"],
377
- affiliateUrl: "#",
378
- }),
379
- },
380
- /** Card display layout */
381
- layout: {
382
- type: String,
383
- default: "card",
384
- validator: (v) => ["card", "row", "compact"].includes(v),
385
- },
386
- /** Show pros list */
387
- showPros: {
388
- type: Boolean,
389
- default: true,
390
- },
391
- /** Show cons list */
392
- showCons: {
393
- type: Boolean,
394
- default: false,
395
- },
396
- /** Show star rating */
397
- showRating: {
398
- type: Boolean,
399
- default: true,
400
- },
401
- /** Show price */
402
- showPrice: {
403
- type: Boolean,
404
- default: true,
405
- },
406
- /** CTA button label */
407
- buttonLabel: {
408
- type: String,
409
- default: "Check Price on Amazon",
410
- },
411
- /** CTA button trailing icon */
412
- buttonIcon: {
413
- type: String,
414
- default: "i-lucide-external-link",
415
- },
416
- /** Small disclosure text shown below the button */
417
- disclosure: {
418
- type: String,
419
- default: "",
420
- },
421
- });
422
-
423
- /** Build the affiliate URL, appending tag param if affiliateTag is set */
424
- const affiliateHref = computed(() => {
425
- const url = props.product.affiliateUrl || "#";
426
- const tag = props.product.affiliateTag;
427
- if (!tag || url === "#") return url;
428
- const separator = url.includes("?") ? "&" : "?";
429
- return `${url}${separator}tag=${tag}`;
430
- });
431
- </script>