@commercejs/ui 0.1.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.
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +15 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +30 -0
- package/dist/runtime/app.config.d.ts +0 -0
- package/dist/runtime/app.config.js +341 -0
- package/dist/runtime/components/auction/CAuctionCard.vue +213 -0
- package/dist/runtime/components/auction/CBidPanel.vue +176 -0
- package/dist/runtime/components/cart/CCartDrawer.vue +223 -0
- package/dist/runtime/components/cart/CCartItem.vue +136 -0
- package/dist/runtime/components/cart/CCartSummary.vue +127 -0
- package/dist/runtime/components/cart/CQuantitySelector.vue +110 -0
- package/dist/runtime/components/category/CCategoryFilter.vue +123 -0
- package/dist/runtime/components/checkout/CAddressForm.vue +186 -0
- package/dist/runtime/components/checkout/CCheckoutStepper.vue +84 -0
- package/dist/runtime/components/common/CEmptyState.vue +81 -0
- package/dist/runtime/components/common/CProductTypeBadge.vue +37 -0
- package/dist/runtime/components/event/CEventCard.vue +129 -0
- package/dist/runtime/components/gift-card/CGiftCardBalance.vue +119 -0
- package/dist/runtime/components/gift-card/CGiftCardForm.vue +157 -0
- package/dist/runtime/components/gift-card/CGiftCardForm.vue.backup +138 -0
- package/dist/runtime/components/marketing/CHeroBanner.vue +142 -0
- package/dist/runtime/components/navigation/CSearchBar.vue +127 -0
- package/dist/runtime/components/order/COrderCard.vue +117 -0
- package/dist/runtime/components/order/COrderTimeline.vue +99 -0
- package/dist/runtime/components/product/CProductCard.vue +206 -0
- package/dist/runtime/components/product/CProductGallery.vue +110 -0
- package/dist/runtime/components/product/CProductGrid.vue +82 -0
- package/dist/runtime/components/product/CProductOptions.vue +101 -0
- package/dist/runtime/components/product/CProductPrice.vue +87 -0
- package/dist/runtime/components/promotion/CCouponInput.vue +104 -0
- package/dist/runtime/components/promotion/CPromoBanner.vue +153 -0
- package/dist/runtime/components/rental/CRentalBookingForm.vue +214 -0
- package/dist/runtime/components/rental/CRentalCard.vue +146 -0
- package/dist/runtime/components/review/CReviewCard.vue +96 -0
- package/dist/runtime/components/review/CReviewStars.vue +106 -0
- package/dist/runtime/components/subscription/CSubscriptionCard.vue +137 -0
- package/dist/runtime/components/wholesale/CPriceTierTable.vue +88 -0
- package/dist/runtime/components/wholesale/CQuoteRequestForm.vue +148 -0
- package/dist/runtime/components/wishlist/CWishlistGrid.vue +96 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +41 -0
- package/src/module.ts +52 -0
- package/src/runtime/app.config.ts +392 -0
- package/src/runtime/components/auction/CAuctionCard.vue +213 -0
- package/src/runtime/components/auction/CBidPanel.vue +176 -0
- package/src/runtime/components/cart/CCartDrawer.vue +223 -0
- package/src/runtime/components/cart/CCartItem.vue +136 -0
- package/src/runtime/components/cart/CCartSummary.vue +127 -0
- package/src/runtime/components/cart/CQuantitySelector.vue +110 -0
- package/src/runtime/components/category/CCategoryFilter.vue +123 -0
- package/src/runtime/components/checkout/CAddressForm.vue +186 -0
- package/src/runtime/components/checkout/CCheckoutStepper.vue +84 -0
- package/src/runtime/components/common/CEmptyState.vue +81 -0
- package/src/runtime/components/common/CProductTypeBadge.vue +37 -0
- package/src/runtime/components/event/CEventCard.vue +129 -0
- package/src/runtime/components/gift-card/CGiftCardBalance.vue +119 -0
- package/src/runtime/components/gift-card/CGiftCardForm.vue +157 -0
- package/src/runtime/components/gift-card/CGiftCardForm.vue.backup +138 -0
- package/src/runtime/components/marketing/CHeroBanner.vue +142 -0
- package/src/runtime/components/navigation/CSearchBar.vue +127 -0
- package/src/runtime/components/order/COrderCard.vue +117 -0
- package/src/runtime/components/order/COrderTimeline.vue +99 -0
- package/src/runtime/components/product/CProductCard.vue +206 -0
- package/src/runtime/components/product/CProductGallery.vue +110 -0
- package/src/runtime/components/product/CProductGrid.vue +82 -0
- package/src/runtime/components/product/CProductOptions.vue +101 -0
- package/src/runtime/components/product/CProductPrice.vue +87 -0
- package/src/runtime/components/promotion/CCouponInput.vue +104 -0
- package/src/runtime/components/promotion/CPromoBanner.vue +153 -0
- package/src/runtime/components/rental/CRentalBookingForm.vue +214 -0
- package/src/runtime/components/rental/CRentalCard.vue +146 -0
- package/src/runtime/components/review/CReviewCard.vue +96 -0
- package/src/runtime/components/review/CReviewStars.vue +106 -0
- package/src/runtime/components/subscription/CSubscriptionCard.vue +137 -0
- package/src/runtime/components/wholesale/CPriceTierTable.vue +88 -0
- package/src/runtime/components/wholesale/CQuoteRequestForm.vue +148 -0
- package/src/runtime/components/wishlist/CWishlistGrid.vue +96 -0
package/dist/module.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface CommerceUIModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Prefix for all commerce UI components.
|
|
6
|
+
* @default 'C'
|
|
7
|
+
* @example 'Commerce' → <CommerceProductCard />
|
|
8
|
+
* @example 'U' → <UProductCard /> (for upstream Nuxt UI)
|
|
9
|
+
*/
|
|
10
|
+
prefix?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const commerceUIModule: NuxtModule<CommerceUIModuleOptions>;
|
|
13
|
+
|
|
14
|
+
export { commerceUIModule as default };
|
|
15
|
+
export type { CommerceUIModuleOptions };
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NuxtModule } from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface CommerceUIModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Prefix for all commerce UI components.
|
|
6
|
+
* @default 'C'
|
|
7
|
+
* @example 'Commerce' → <CommerceProductCard />
|
|
8
|
+
* @example 'U' → <UProductCard /> (for upstream Nuxt UI)
|
|
9
|
+
*/
|
|
10
|
+
prefix?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const commerceUIModule: NuxtModule<CommerceUIModuleOptions>;
|
|
13
|
+
|
|
14
|
+
export { commerceUIModule as default };
|
|
15
|
+
export type { CommerceUIModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, installModule, addComponentsDir } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
const commerceUIModule = defineNuxtModule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "@commercejs/ui",
|
|
6
|
+
configKey: "commerceUI",
|
|
7
|
+
compatibility: {
|
|
8
|
+
nuxt: ">=3.0.0"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
defaults: {
|
|
12
|
+
prefix: "C"
|
|
13
|
+
},
|
|
14
|
+
async setup(options, nuxt) {
|
|
15
|
+
const { resolve } = createResolver(import.meta.url);
|
|
16
|
+
const prefix = options.prefix || "C";
|
|
17
|
+
await installModule("@nuxt/ui");
|
|
18
|
+
addComponentsDir({
|
|
19
|
+
path: resolve("./runtime/components"),
|
|
20
|
+
prefix,
|
|
21
|
+
// Scan subdirectories (product/, cart/, etc.)
|
|
22
|
+
pathPrefix: false
|
|
23
|
+
});
|
|
24
|
+
nuxt.hook("app:resolve", (app) => {
|
|
25
|
+
app.configs.push(resolve("./runtime/app.config"));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export { commerceUIModule as default };
|
|
File without changes
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
export default defineAppConfig({
|
|
2
|
+
ui: {
|
|
3
|
+
// ---- Product Components ----
|
|
4
|
+
productCard: {
|
|
5
|
+
slots: {
|
|
6
|
+
root: "group relative rounded-lg overflow-hidden ring ring-default transition-all duration-200",
|
|
7
|
+
imageWrapper: "relative aspect-square overflow-hidden bg-elevated",
|
|
8
|
+
image: "size-full object-cover transition-transform duration-300 group-hover:scale-105",
|
|
9
|
+
badge: "absolute top-3 start-3 z-10",
|
|
10
|
+
overlay: "absolute inset-x-0 bottom-0 p-3 flex justify-end opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-10",
|
|
11
|
+
body: "p-4 space-y-1.5",
|
|
12
|
+
title: "font-medium text-sm text-highlighted line-clamp-2 transition-colors",
|
|
13
|
+
price: "font-bold text-highlighted",
|
|
14
|
+
originalPrice: "text-xs text-muted line-through ms-2",
|
|
15
|
+
priceWrapper: "flex items-baseline gap-1",
|
|
16
|
+
rating: "flex items-center gap-1 text-xs text-muted"
|
|
17
|
+
},
|
|
18
|
+
variants: {
|
|
19
|
+
variant: {
|
|
20
|
+
outline: { root: "bg-default hover:shadow-lg hover:shadow-default/10" },
|
|
21
|
+
soft: { root: "bg-elevated/50 ring-0 hover:bg-elevated" },
|
|
22
|
+
ghost: { root: "ring-0 hover:bg-elevated/50" }
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
sm: { body: "p-3 space-y-1", title: "text-xs", price: "text-sm" },
|
|
26
|
+
md: { body: "p-4 space-y-1.5", title: "text-sm", price: "text-base" },
|
|
27
|
+
lg: { body: "p-5 space-y-2", title: "text-base", price: "text-lg" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: { variant: "outline", size: "md" }
|
|
31
|
+
},
|
|
32
|
+
productPrice: {
|
|
33
|
+
slots: {
|
|
34
|
+
root: "inline-flex items-baseline gap-1.5",
|
|
35
|
+
current: "font-bold text-highlighted",
|
|
36
|
+
original: "line-through text-muted",
|
|
37
|
+
discount: "font-medium text-error"
|
|
38
|
+
},
|
|
39
|
+
variants: {
|
|
40
|
+
size: {
|
|
41
|
+
xs: { current: "text-xs", original: "text-xs", discount: "text-xs" },
|
|
42
|
+
sm: { current: "text-sm", original: "text-xs", discount: "text-xs" },
|
|
43
|
+
md: { current: "text-base", original: "text-sm", discount: "text-sm" },
|
|
44
|
+
lg: { current: "text-lg", original: "text-sm", discount: "text-sm" },
|
|
45
|
+
xl: { current: "text-2xl", original: "text-base", discount: "text-base" }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
defaultVariants: { size: "md" }
|
|
49
|
+
},
|
|
50
|
+
productGallery: {
|
|
51
|
+
slots: {
|
|
52
|
+
root: "flex flex-col gap-3",
|
|
53
|
+
main: "relative rounded-lg overflow-hidden bg-elevated aspect-square",
|
|
54
|
+
mainImage: "size-full object-contain",
|
|
55
|
+
thumbnails: "flex gap-2 overflow-x-auto",
|
|
56
|
+
thumbnail: "size-16 rounded-md overflow-hidden ring ring-transparent cursor-pointer shrink-0 transition-all",
|
|
57
|
+
thumbnailActive: "ring-primary"
|
|
58
|
+
},
|
|
59
|
+
variants: {
|
|
60
|
+
thumbnailPosition: {
|
|
61
|
+
bottom: { root: "flex-col" },
|
|
62
|
+
start: { root: "flex-row-reverse", thumbnails: "flex-col overflow-y-auto max-h-[400px]" }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
productOptions: {
|
|
67
|
+
slots: {
|
|
68
|
+
root: "space-y-4",
|
|
69
|
+
group: "space-y-2",
|
|
70
|
+
label: "text-sm font-medium text-highlighted",
|
|
71
|
+
values: "flex flex-wrap gap-2"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
productGrid: {
|
|
75
|
+
slots: {
|
|
76
|
+
root: "",
|
|
77
|
+
empty: ""
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
// ---- Cart Components ----
|
|
81
|
+
quantitySelector: {
|
|
82
|
+
slots: {
|
|
83
|
+
root: "inline-flex items-center gap-0.5",
|
|
84
|
+
button: "",
|
|
85
|
+
input: "w-10 text-center text-sm font-medium bg-transparent border-0 focus:ring-0 text-highlighted"
|
|
86
|
+
},
|
|
87
|
+
variants: {
|
|
88
|
+
size: {
|
|
89
|
+
sm: { input: "w-8 text-xs" },
|
|
90
|
+
md: { input: "w-10 text-sm" },
|
|
91
|
+
lg: { input: "w-12 text-base" }
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
defaultVariants: { size: "md" }
|
|
95
|
+
},
|
|
96
|
+
cartItem: {
|
|
97
|
+
slots: {
|
|
98
|
+
root: "flex gap-4",
|
|
99
|
+
imageWrapper: "shrink-0 rounded-lg overflow-hidden bg-elevated",
|
|
100
|
+
image: "size-full object-cover",
|
|
101
|
+
body: "flex-1 min-w-0",
|
|
102
|
+
title: "font-medium text-sm text-highlighted line-clamp-1",
|
|
103
|
+
variant: "text-xs text-muted",
|
|
104
|
+
priceWrapper: "mt-1",
|
|
105
|
+
actions: "flex items-center justify-between mt-2"
|
|
106
|
+
},
|
|
107
|
+
variants: {
|
|
108
|
+
size: {
|
|
109
|
+
sm: { imageWrapper: "size-16", title: "text-xs" },
|
|
110
|
+
md: { imageWrapper: "size-20", title: "text-sm" },
|
|
111
|
+
lg: { imageWrapper: "size-24", title: "text-base" }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
defaultVariants: { size: "md" }
|
|
115
|
+
},
|
|
116
|
+
cartSummary: {
|
|
117
|
+
slots: {
|
|
118
|
+
root: "space-y-4",
|
|
119
|
+
title: "text-lg font-semibold text-highlighted",
|
|
120
|
+
lineItem: "flex justify-between text-sm",
|
|
121
|
+
lineLabel: "text-muted",
|
|
122
|
+
lineValue: "font-medium text-highlighted",
|
|
123
|
+
separator: "",
|
|
124
|
+
total: "flex justify-between text-base font-bold",
|
|
125
|
+
totalLabel: "text-highlighted",
|
|
126
|
+
totalValue: "text-highlighted",
|
|
127
|
+
actions: "pt-4"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
// ---- Checkout Components ----
|
|
131
|
+
checkoutStepper: {
|
|
132
|
+
slots: {
|
|
133
|
+
root: ""
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
addressForm: {
|
|
137
|
+
slots: {
|
|
138
|
+
root: "space-y-4",
|
|
139
|
+
row: "grid grid-cols-1 sm:grid-cols-2 gap-4",
|
|
140
|
+
field: ""
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
// ---- Review Components ----
|
|
144
|
+
reviewStars: {
|
|
145
|
+
slots: {
|
|
146
|
+
root: "",
|
|
147
|
+
star: "",
|
|
148
|
+
starFilled: "",
|
|
149
|
+
starEmpty: "",
|
|
150
|
+
count: "text-xs text-muted ms-1"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
reviewCard: {
|
|
154
|
+
slots: {
|
|
155
|
+
root: "space-y-2",
|
|
156
|
+
header: "space-y-1",
|
|
157
|
+
author: "font-medium text-sm text-highlighted",
|
|
158
|
+
date: "text-xs text-muted",
|
|
159
|
+
title: "font-medium text-highlighted",
|
|
160
|
+
body: "text-sm text-muted leading-relaxed",
|
|
161
|
+
verified: ""
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
// ---- Navigation Components ----
|
|
165
|
+
searchBar: {
|
|
166
|
+
slots: {
|
|
167
|
+
root: ""
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
// ---- Category Components ----
|
|
171
|
+
categoryFilter: {
|
|
172
|
+
slots: {
|
|
173
|
+
root: "space-y-4",
|
|
174
|
+
group: "space-y-2",
|
|
175
|
+
groupTitle: "text-sm font-semibold text-highlighted",
|
|
176
|
+
values: "space-y-1.5",
|
|
177
|
+
value: "flex items-center gap-2 text-sm cursor-pointer",
|
|
178
|
+
count: "text-xs text-muted ms-auto",
|
|
179
|
+
showMore: "text-xs text-primary font-medium cursor-pointer hover:underline"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
// ---- Marketing Components ----
|
|
183
|
+
heroBanner: {
|
|
184
|
+
slots: {
|
|
185
|
+
root: "",
|
|
186
|
+
background: "",
|
|
187
|
+
overlay: "",
|
|
188
|
+
content: "",
|
|
189
|
+
title: "",
|
|
190
|
+
subtitle: "",
|
|
191
|
+
actions: ""
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
// ---- Common Components ----
|
|
195
|
+
emptyState: {
|
|
196
|
+
slots: {
|
|
197
|
+
root: "",
|
|
198
|
+
icon: "",
|
|
199
|
+
title: "",
|
|
200
|
+
description: ""
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
// ---- Auction Components ----
|
|
204
|
+
auctionCard: {
|
|
205
|
+
slots: {
|
|
206
|
+
root: "",
|
|
207
|
+
imageWrapper: "",
|
|
208
|
+
image: "",
|
|
209
|
+
statusBadge: "",
|
|
210
|
+
body: "p-4 space-y-2",
|
|
211
|
+
title: "font-medium text-sm text-highlighted line-clamp-2",
|
|
212
|
+
bidInfo: "space-y-1",
|
|
213
|
+
currentBid: "font-bold text-lg text-highlighted",
|
|
214
|
+
bidCount: "text-xs text-muted",
|
|
215
|
+
timer: "",
|
|
216
|
+
actions: "flex gap-2 pt-1"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
bidPanel: {
|
|
220
|
+
slots: {
|
|
221
|
+
root: "space-y-6",
|
|
222
|
+
currentBid: "text-center p-6 rounded-xl bg-elevated ring ring-default",
|
|
223
|
+
form: "space-y-4",
|
|
224
|
+
history: "space-y-2"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
// ---- Rental Components ----
|
|
228
|
+
rentalCard: {
|
|
229
|
+
slots: {
|
|
230
|
+
root: "",
|
|
231
|
+
imageWrapper: "",
|
|
232
|
+
image: "",
|
|
233
|
+
body: "p-4 space-y-2",
|
|
234
|
+
title: "font-medium text-sm text-highlighted line-clamp-2",
|
|
235
|
+
pricing: "flex items-baseline gap-1",
|
|
236
|
+
meta: "flex flex-wrap gap-2 text-xs",
|
|
237
|
+
actions: ""
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
rentalBookingForm: {
|
|
241
|
+
slots: {
|
|
242
|
+
root: "space-y-5",
|
|
243
|
+
dates: "",
|
|
244
|
+
summary: "rounded-xl bg-elevated p-4 space-y-2"
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
// ---- Subscription Components ----
|
|
248
|
+
subscriptionCard: {
|
|
249
|
+
slots: {
|
|
250
|
+
root: "",
|
|
251
|
+
header: "text-center",
|
|
252
|
+
pricing: "text-center py-4",
|
|
253
|
+
features: "",
|
|
254
|
+
actions: ""
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
// ---- Wholesale Components ----
|
|
258
|
+
priceTierTable: {
|
|
259
|
+
slots: {
|
|
260
|
+
root: "overflow-hidden rounded-lg ring ring-default",
|
|
261
|
+
row: "",
|
|
262
|
+
activeRow: "bg-primary/5 font-medium"
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
quoteRequestForm: {
|
|
266
|
+
slots: {
|
|
267
|
+
root: "space-y-6",
|
|
268
|
+
items: "space-y-4",
|
|
269
|
+
contact: "grid grid-cols-1 sm:grid-cols-2 gap-4"
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
// ---- Gift Card Components ----
|
|
273
|
+
giftCardForm: {
|
|
274
|
+
slots: {
|
|
275
|
+
root: "space-y-6",
|
|
276
|
+
amounts: "space-y-3",
|
|
277
|
+
recipient: "space-y-3"
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
giftCardBalance: {
|
|
281
|
+
slots: {
|
|
282
|
+
root: "space-y-4",
|
|
283
|
+
card: "rounded-xl bg-elevated ring ring-default p-5 space-y-3",
|
|
284
|
+
form: "flex gap-2"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
// ---- Order Components ----
|
|
288
|
+
orderCard: {
|
|
289
|
+
slots: {
|
|
290
|
+
root: "rounded-xl ring ring-default bg-default overflow-hidden",
|
|
291
|
+
header: "flex items-center justify-between px-5 py-3 bg-elevated",
|
|
292
|
+
items: "px-5 py-4",
|
|
293
|
+
footer: "flex items-center justify-between px-5 py-3 bg-elevated"
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
orderTimeline: {
|
|
297
|
+
slots: {
|
|
298
|
+
root: "relative",
|
|
299
|
+
entry: "flex gap-4 pb-6 last:pb-0",
|
|
300
|
+
dot: "size-8 rounded-full flex items-center justify-center ring-4 ring-default bg-default z-10",
|
|
301
|
+
line: "w-0.5 flex-1 bg-default mt-1"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
// ---- Promotion Components ----
|
|
305
|
+
promoBanner: {
|
|
306
|
+
slots: {
|
|
307
|
+
root: "relative overflow-hidden rounded-xl",
|
|
308
|
+
content: "",
|
|
309
|
+
timer: "flex gap-2",
|
|
310
|
+
cta: ""
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
couponInput: {
|
|
314
|
+
slots: {
|
|
315
|
+
root: "space-y-2",
|
|
316
|
+
input: "flex-1 font-mono uppercase",
|
|
317
|
+
applied: "flex items-center justify-between p-3 rounded-lg bg-success/10 ring ring-success/30"
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
// ---- Event Components ----
|
|
321
|
+
eventCard: {
|
|
322
|
+
slots: {
|
|
323
|
+
root: "",
|
|
324
|
+
imageWrapper: "",
|
|
325
|
+
dateOverlay: "",
|
|
326
|
+
body: "p-4 space-y-2",
|
|
327
|
+
title: "font-medium text-sm text-highlighted line-clamp-2",
|
|
328
|
+
meta: "space-y-1 text-xs text-muted",
|
|
329
|
+
actions: ""
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
// ---- Wishlist Components ----
|
|
333
|
+
wishlistGrid: {
|
|
334
|
+
slots: {
|
|
335
|
+
root: "",
|
|
336
|
+
item: "relative group",
|
|
337
|
+
actions: ""
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Product, AuctionProductMeta } from '@commercejs/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CAuctionCard — Product card for auction items.
|
|
6
|
+
* Shows current bid, bid count, time remaining, and auction status.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface AuctionCardProps {
|
|
10
|
+
product: Product
|
|
11
|
+
/** Override auction meta (optional, reads from product.auction by default) */
|
|
12
|
+
auction?: AuctionProductMeta
|
|
13
|
+
/** Show buy-now button */
|
|
14
|
+
showBuyNow?: boolean
|
|
15
|
+
/** Per-instance theme overrides */
|
|
16
|
+
ui?: Partial<{
|
|
17
|
+
root: any
|
|
18
|
+
imageWrapper: any
|
|
19
|
+
image: any
|
|
20
|
+
statusBadge: any
|
|
21
|
+
body: any
|
|
22
|
+
title: any
|
|
23
|
+
bidInfo: any
|
|
24
|
+
currentBid: any
|
|
25
|
+
bidCount: any
|
|
26
|
+
timer: any
|
|
27
|
+
actions: any
|
|
28
|
+
}>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<AuctionCardProps>(), {
|
|
32
|
+
showBuyNow: true,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits<{
|
|
36
|
+
'bid': [product: Product]
|
|
37
|
+
'buy-now': [product: Product]
|
|
38
|
+
}>()
|
|
39
|
+
|
|
40
|
+
const auctionMeta = computed(() => props.auction || props.product.auction)
|
|
41
|
+
|
|
42
|
+
function t(value: any): string {
|
|
43
|
+
if (!value) return ''
|
|
44
|
+
if (typeof value === 'string') return value
|
|
45
|
+
return value.en || value.ar || Object.values(value)[0] || ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const productName = computed(() => t(props.product.name))
|
|
49
|
+
const mainImage = computed(() => props.product.primaryImage || props.product.gallery?.[0])
|
|
50
|
+
|
|
51
|
+
// Time remaining
|
|
52
|
+
const timeRemaining = ref('')
|
|
53
|
+
let timer: ReturnType<typeof setInterval>
|
|
54
|
+
|
|
55
|
+
function updateTimer() {
|
|
56
|
+
if (!auctionMeta.value) return
|
|
57
|
+
const end = new Date(auctionMeta.value.endsAt).getTime()
|
|
58
|
+
const now = Date.now()
|
|
59
|
+
const diff = end - now
|
|
60
|
+
|
|
61
|
+
if (diff <= 0) {
|
|
62
|
+
timeRemaining.value = 'Ended'
|
|
63
|
+
clearInterval(timer)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const days = Math.floor(diff / 86400000)
|
|
68
|
+
const hours = Math.floor((diff % 86400000) / 3600000)
|
|
69
|
+
const mins = Math.floor((diff % 3600000) / 60000)
|
|
70
|
+
const secs = Math.floor((diff % 60000) / 1000)
|
|
71
|
+
|
|
72
|
+
if (days > 0) {
|
|
73
|
+
timeRemaining.value = `${days}d ${hours}h`
|
|
74
|
+
} else if (hours > 0) {
|
|
75
|
+
timeRemaining.value = `${hours}h ${mins}m`
|
|
76
|
+
} else {
|
|
77
|
+
timeRemaining.value = `${mins}m ${secs}s`
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onMounted(() => {
|
|
82
|
+
updateTimer()
|
|
83
|
+
timer = setInterval(updateTimer, 1000)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
onUnmounted(() => clearInterval(timer))
|
|
87
|
+
|
|
88
|
+
const statusColor = computed(() => {
|
|
89
|
+
const map: Record<string, string> = {
|
|
90
|
+
upcoming: 'info',
|
|
91
|
+
active: 'success',
|
|
92
|
+
ended: 'neutral',
|
|
93
|
+
sold: 'primary',
|
|
94
|
+
cancelled: 'error',
|
|
95
|
+
reserve_not_met: 'warning',
|
|
96
|
+
}
|
|
97
|
+
return map[auctionMeta.value?.status || ''] || 'neutral'
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const statusLabel = computed(() => {
|
|
101
|
+
const map: Record<string, string> = {
|
|
102
|
+
upcoming: 'Upcoming',
|
|
103
|
+
active: 'Live',
|
|
104
|
+
ended: 'Ended',
|
|
105
|
+
sold: 'Sold',
|
|
106
|
+
cancelled: 'Cancelled',
|
|
107
|
+
reserve_not_met: 'Reserve Not Met',
|
|
108
|
+
}
|
|
109
|
+
return map[auctionMeta.value?.status || ''] || auctionMeta.value?.status
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Resolve theme from app.config
|
|
113
|
+
const appConfig = useAppConfig()
|
|
114
|
+
const theme = computed(() => (appConfig.ui as any)?.auctionCard ?? {})
|
|
115
|
+
|
|
116
|
+
const slotClasses = computed(() => {
|
|
117
|
+
const base = theme.value?.slots ?? {}
|
|
118
|
+
const merge = (slot: string) => [base[slot], props.ui?.[slot as keyof typeof props.ui]]
|
|
119
|
+
return {
|
|
120
|
+
root: merge('root'),
|
|
121
|
+
imageWrapper: merge('imageWrapper'),
|
|
122
|
+
image: merge('image'),
|
|
123
|
+
statusBadge: merge('statusBadge'),
|
|
124
|
+
body: merge('body'),
|
|
125
|
+
title: merge('title'),
|
|
126
|
+
bidInfo: merge('bidInfo'),
|
|
127
|
+
currentBid: merge('currentBid'),
|
|
128
|
+
bidCount: merge('bidCount'),
|
|
129
|
+
timer: merge('timer'),
|
|
130
|
+
actions: merge('actions'),
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<template>
|
|
136
|
+
<div :class="['group relative rounded-lg overflow-hidden ring ring-default bg-default hover:shadow-lg transition-all duration-200', slotClasses.root]">
|
|
137
|
+
<!-- Image -->
|
|
138
|
+
<div :class="['relative aspect-square overflow-hidden bg-elevated', slotClasses.imageWrapper]">
|
|
139
|
+
<img
|
|
140
|
+
v-if="mainImage"
|
|
141
|
+
:src="mainImage.url"
|
|
142
|
+
:alt="mainImage.alt || productName"
|
|
143
|
+
:class="['size-full object-cover transition-transform duration-300 group-hover:scale-105', slotClasses.image]"
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
<!-- Status badge -->
|
|
147
|
+
<slot name="status" :status="auctionMeta?.status" :label="statusLabel">
|
|
148
|
+
<UBadge
|
|
149
|
+
v-if="auctionMeta"
|
|
150
|
+
:color="statusColor as any"
|
|
151
|
+
size="sm"
|
|
152
|
+
:class="['absolute top-3 start-3', slotClasses.statusBadge]"
|
|
153
|
+
>
|
|
154
|
+
<UIcon v-if="auctionMeta.status === 'active'" name="i-heroicons-signal" class="me-0.5 animate-pulse" />
|
|
155
|
+
{{ statusLabel }}
|
|
156
|
+
</UBadge>
|
|
157
|
+
</slot>
|
|
158
|
+
|
|
159
|
+
<!-- Timer -->
|
|
160
|
+
<slot name="timer" :remaining="timeRemaining">
|
|
161
|
+
<div v-if="auctionMeta?.status === 'active'" :class="['absolute bottom-3 end-3 bg-black/70 backdrop-blur-sm text-white text-xs font-mono px-2 py-1 rounded', slotClasses.timer]">
|
|
162
|
+
<UIcon name="i-heroicons-clock" class="me-1" />
|
|
163
|
+
{{ timeRemaining }}
|
|
164
|
+
</div>
|
|
165
|
+
</slot>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- Body -->
|
|
169
|
+
<div :class="['p-4 space-y-2', slotClasses.body]">
|
|
170
|
+
<slot name="title" :name="productName">
|
|
171
|
+
<h3 :class="['font-medium text-sm text-highlighted line-clamp-2', slotClasses.title]">{{ productName }}</h3>
|
|
172
|
+
</slot>
|
|
173
|
+
|
|
174
|
+
<!-- Bid info -->
|
|
175
|
+
<slot name="bid-info" :auction="auctionMeta">
|
|
176
|
+
<div v-if="auctionMeta" :class="['space-y-1', slotClasses.bidInfo]">
|
|
177
|
+
<div class="flex items-baseline justify-between">
|
|
178
|
+
<span class="text-xs text-muted">{{ auctionMeta.bidCount > 0 ? 'Current Bid' : 'Starting Price' }}</span>
|
|
179
|
+
<span :class="['font-bold text-lg text-highlighted', slotClasses.currentBid]">
|
|
180
|
+
{{ auctionMeta.currentBid?.formatted || auctionMeta.startingPrice.formatted }}
|
|
181
|
+
</span>
|
|
182
|
+
</div>
|
|
183
|
+
<span :class="['text-xs text-muted', slotClasses.bidCount]">
|
|
184
|
+
{{ auctionMeta.bidCount }} bid{{ auctionMeta.bidCount !== 1 ? 's' : '' }}
|
|
185
|
+
</span>
|
|
186
|
+
</div>
|
|
187
|
+
</slot>
|
|
188
|
+
|
|
189
|
+
<!-- Actions -->
|
|
190
|
+
<slot name="actions" :auction="auctionMeta">
|
|
191
|
+
<div v-if="auctionMeta?.status === 'active'" :class="['flex gap-2 pt-1', slotClasses.actions]">
|
|
192
|
+
<UButton
|
|
193
|
+
size="sm"
|
|
194
|
+
color="primary"
|
|
195
|
+
class="flex-1"
|
|
196
|
+
@click="emit('bid', product)"
|
|
197
|
+
>
|
|
198
|
+
Place Bid
|
|
199
|
+
</UButton>
|
|
200
|
+
<UButton
|
|
201
|
+
v-if="showBuyNow && auctionMeta.buyNowPrice"
|
|
202
|
+
size="sm"
|
|
203
|
+
variant="outline"
|
|
204
|
+
color="neutral"
|
|
205
|
+
@click="emit('buy-now', product)"
|
|
206
|
+
>
|
|
207
|
+
Buy Now {{ auctionMeta.buyNowPrice.formatted }}
|
|
208
|
+
</UButton>
|
|
209
|
+
</div>
|
|
210
|
+
</slot>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</template>
|