@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.
Files changed (80) hide show
  1. package/dist/module.cjs +5 -0
  2. package/dist/module.d.mts +15 -0
  3. package/dist/module.d.ts +15 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +30 -0
  6. package/dist/runtime/app.config.d.ts +0 -0
  7. package/dist/runtime/app.config.js +341 -0
  8. package/dist/runtime/components/auction/CAuctionCard.vue +213 -0
  9. package/dist/runtime/components/auction/CBidPanel.vue +176 -0
  10. package/dist/runtime/components/cart/CCartDrawer.vue +223 -0
  11. package/dist/runtime/components/cart/CCartItem.vue +136 -0
  12. package/dist/runtime/components/cart/CCartSummary.vue +127 -0
  13. package/dist/runtime/components/cart/CQuantitySelector.vue +110 -0
  14. package/dist/runtime/components/category/CCategoryFilter.vue +123 -0
  15. package/dist/runtime/components/checkout/CAddressForm.vue +186 -0
  16. package/dist/runtime/components/checkout/CCheckoutStepper.vue +84 -0
  17. package/dist/runtime/components/common/CEmptyState.vue +81 -0
  18. package/dist/runtime/components/common/CProductTypeBadge.vue +37 -0
  19. package/dist/runtime/components/event/CEventCard.vue +129 -0
  20. package/dist/runtime/components/gift-card/CGiftCardBalance.vue +119 -0
  21. package/dist/runtime/components/gift-card/CGiftCardForm.vue +157 -0
  22. package/dist/runtime/components/gift-card/CGiftCardForm.vue.backup +138 -0
  23. package/dist/runtime/components/marketing/CHeroBanner.vue +142 -0
  24. package/dist/runtime/components/navigation/CSearchBar.vue +127 -0
  25. package/dist/runtime/components/order/COrderCard.vue +117 -0
  26. package/dist/runtime/components/order/COrderTimeline.vue +99 -0
  27. package/dist/runtime/components/product/CProductCard.vue +206 -0
  28. package/dist/runtime/components/product/CProductGallery.vue +110 -0
  29. package/dist/runtime/components/product/CProductGrid.vue +82 -0
  30. package/dist/runtime/components/product/CProductOptions.vue +101 -0
  31. package/dist/runtime/components/product/CProductPrice.vue +87 -0
  32. package/dist/runtime/components/promotion/CCouponInput.vue +104 -0
  33. package/dist/runtime/components/promotion/CPromoBanner.vue +153 -0
  34. package/dist/runtime/components/rental/CRentalBookingForm.vue +214 -0
  35. package/dist/runtime/components/rental/CRentalCard.vue +146 -0
  36. package/dist/runtime/components/review/CReviewCard.vue +96 -0
  37. package/dist/runtime/components/review/CReviewStars.vue +106 -0
  38. package/dist/runtime/components/subscription/CSubscriptionCard.vue +137 -0
  39. package/dist/runtime/components/wholesale/CPriceTierTable.vue +88 -0
  40. package/dist/runtime/components/wholesale/CQuoteRequestForm.vue +148 -0
  41. package/dist/runtime/components/wishlist/CWishlistGrid.vue +96 -0
  42. package/dist/types.d.mts +7 -0
  43. package/dist/types.d.ts +7 -0
  44. package/package.json +41 -0
  45. package/src/module.ts +52 -0
  46. package/src/runtime/app.config.ts +392 -0
  47. package/src/runtime/components/auction/CAuctionCard.vue +213 -0
  48. package/src/runtime/components/auction/CBidPanel.vue +176 -0
  49. package/src/runtime/components/cart/CCartDrawer.vue +223 -0
  50. package/src/runtime/components/cart/CCartItem.vue +136 -0
  51. package/src/runtime/components/cart/CCartSummary.vue +127 -0
  52. package/src/runtime/components/cart/CQuantitySelector.vue +110 -0
  53. package/src/runtime/components/category/CCategoryFilter.vue +123 -0
  54. package/src/runtime/components/checkout/CAddressForm.vue +186 -0
  55. package/src/runtime/components/checkout/CCheckoutStepper.vue +84 -0
  56. package/src/runtime/components/common/CEmptyState.vue +81 -0
  57. package/src/runtime/components/common/CProductTypeBadge.vue +37 -0
  58. package/src/runtime/components/event/CEventCard.vue +129 -0
  59. package/src/runtime/components/gift-card/CGiftCardBalance.vue +119 -0
  60. package/src/runtime/components/gift-card/CGiftCardForm.vue +157 -0
  61. package/src/runtime/components/gift-card/CGiftCardForm.vue.backup +138 -0
  62. package/src/runtime/components/marketing/CHeroBanner.vue +142 -0
  63. package/src/runtime/components/navigation/CSearchBar.vue +127 -0
  64. package/src/runtime/components/order/COrderCard.vue +117 -0
  65. package/src/runtime/components/order/COrderTimeline.vue +99 -0
  66. package/src/runtime/components/product/CProductCard.vue +206 -0
  67. package/src/runtime/components/product/CProductGallery.vue +110 -0
  68. package/src/runtime/components/product/CProductGrid.vue +82 -0
  69. package/src/runtime/components/product/CProductOptions.vue +101 -0
  70. package/src/runtime/components/product/CProductPrice.vue +87 -0
  71. package/src/runtime/components/promotion/CCouponInput.vue +104 -0
  72. package/src/runtime/components/promotion/CPromoBanner.vue +153 -0
  73. package/src/runtime/components/rental/CRentalBookingForm.vue +214 -0
  74. package/src/runtime/components/rental/CRentalCard.vue +146 -0
  75. package/src/runtime/components/review/CReviewCard.vue +96 -0
  76. package/src/runtime/components/review/CReviewStars.vue +106 -0
  77. package/src/runtime/components/subscription/CSubscriptionCard.vue +137 -0
  78. package/src/runtime/components/wholesale/CPriceTierTable.vue +88 -0
  79. package/src/runtime/components/wholesale/CQuoteRequestForm.vue +148 -0
  80. package/src/runtime/components/wishlist/CWishlistGrid.vue +96 -0
@@ -0,0 +1,88 @@
1
+ <script setup lang="ts">
2
+ import type { PriceTier } from '@commercejs/types'
3
+
4
+ /**
5
+ * CPriceTierTable — Displays volume-based pricing tiers for B2B/wholesale products.
6
+ */
7
+
8
+ export interface PriceTierTableProps {
9
+ /** Price tiers from product.priceTiers */
10
+ tiers: PriceTier[]
11
+ /** Currently selected / applicable quantity */
12
+ currentQuantity?: number
13
+ /** Per-instance theme overrides */
14
+ ui?: Partial<{
15
+ root: any
16
+ row: any
17
+ activeRow: any
18
+ }>
19
+ }
20
+
21
+ const props = withDefaults(defineProps<PriceTierTableProps>(), {
22
+ currentQuantity: 0,
23
+ })
24
+
25
+ function t(value: any): string {
26
+ if (!value) return ''
27
+ if (typeof value === 'string') return value
28
+ return value.en || value.ar || Object.values(value)[0] || ''
29
+ }
30
+
31
+ function isActiveTier(tier: PriceTier): boolean {
32
+ if (props.currentQuantity < tier.minQuantity) return false
33
+ if (tier.maxQuantity && props.currentQuantity > tier.maxQuantity) return false
34
+ return true
35
+ }
36
+
37
+ // Resolve theme from app.config
38
+ const appConfig = useAppConfig()
39
+ const theme = computed(() => (appConfig.ui as any)?.priceTierTable ?? {})
40
+
41
+ const slotClasses = computed(() => {
42
+ const base = theme.value?.slots ?? {}
43
+ const merge = (slot: string) => [base[slot], props.ui?.[slot as keyof typeof props.ui]]
44
+ return {
45
+ root: merge('root'),
46
+ row: merge('row'),
47
+ activeRow: merge('activeRow'),
48
+ }
49
+ })
50
+ </script>
51
+
52
+ <template>
53
+ <div :class="['overflow-hidden rounded-lg ring ring-default', slotClasses.root]">
54
+ <table class="w-full text-sm">
55
+ <thead class="bg-elevated text-muted">
56
+ <tr>
57
+ <th class="text-start py-2 px-3 font-medium">Quantity</th>
58
+ <th class="text-start py-2 px-3 font-medium">Price / Unit</th>
59
+ <th v-if="tiers.some(t => t.label)" class="text-start py-2 px-3 font-medium">Tier</th>
60
+ </tr>
61
+ </thead>
62
+ <tbody>
63
+ <tr
64
+ v-for="(tier, i) in tiers"
65
+ :key="i"
66
+ :class="[
67
+ 'border-t border-default transition-colors',
68
+ isActiveTier(tier) ? 'bg-primary/5 font-medium' : '',
69
+ slotClasses.row,
70
+ isActiveTier(tier) ? slotClasses.activeRow : '',
71
+ ]"
72
+ >
73
+ <td class="py-2.5 px-3 text-highlighted">
74
+ {{ tier.minQuantity }}{{ tier.maxQuantity ? ` – ${tier.maxQuantity}` : '+' }}
75
+ </td>
76
+ <td class="py-2.5 px-3">
77
+ <span :class="isActiveTier(tier) ? 'text-primary font-semibold' : 'text-highlighted'">
78
+ {{ tier.unitPrice.formatted }}
79
+ </span>
80
+ </td>
81
+ <td v-if="tiers.some(t => t.label)" class="py-2.5 px-3 text-muted">
82
+ {{ t(tier.label) }}
83
+ </td>
84
+ </tr>
85
+ </tbody>
86
+ </table>
87
+ </div>
88
+ </template>
@@ -0,0 +1,148 @@
1
+ <script setup lang="ts">
2
+ import type { CreateQuoteInput } from '@commercejs/types'
3
+
4
+ /**
5
+ * CQuoteRequestForm — B2B Request-for-Quote form.
6
+ * Allows buyers to submit RFQ with line items, target prices, and company info.
7
+ */
8
+
9
+ export interface QuoteRequestFormProps {
10
+ /** Pre-populated items (e.g., from cart) */
11
+ initialItems?: Array<{
12
+ productId: string
13
+ productName?: string
14
+ quantity: number
15
+ targetPrice?: number
16
+ }>
17
+ /** Whether the form is submitting */
18
+ loading?: boolean
19
+ /** Per-instance theme overrides */
20
+ ui?: Partial<{
21
+ root: any
22
+ items: any
23
+ contact: any
24
+ }>
25
+ }
26
+
27
+ const props = withDefaults(defineProps<QuoteRequestFormProps>(), {
28
+ initialItems: () => [],
29
+ loading: false,
30
+ })
31
+
32
+ const emit = defineEmits<{
33
+ 'submit': [input: CreateQuoteInput]
34
+ }>()
35
+
36
+ // Form state
37
+ const items = ref(props.initialItems.map(item => ({
38
+ productId: item.productId,
39
+ productName: item.productName || '',
40
+ quantity: item.quantity,
41
+ targetPrice: item.targetPrice,
42
+ note: '',
43
+ })))
44
+
45
+ const companyName = ref('')
46
+ const contactEmail = ref('')
47
+ const note = ref('')
48
+
49
+ function addItem() {
50
+ items.value.push({ productId: '', productName: '', quantity: 1, targetPrice: undefined, note: '' })
51
+ }
52
+
53
+ function removeItem(index: number) {
54
+ items.value.splice(index, 1)
55
+ }
56
+
57
+ function handleSubmit() {
58
+ emit('submit', {
59
+ items: items.value.map(item => ({
60
+ productId: item.productId,
61
+ quantity: item.quantity,
62
+ targetPrice: item.targetPrice,
63
+ note: item.note || undefined,
64
+ })),
65
+ companyName: companyName.value || undefined,
66
+ contactEmail: contactEmail.value,
67
+ note: note.value || undefined,
68
+ })
69
+ }
70
+
71
+ // Resolve theme from app.config
72
+ const appConfig = useAppConfig()
73
+ const theme = computed(() => (appConfig.ui as any)?.quoteRequestForm ?? {})
74
+
75
+ const slotClasses = computed(() => {
76
+ const base = theme.value?.slots ?? {}
77
+ const merge = (slot: string) => [base[slot], props.ui?.[slot as keyof typeof props.ui]]
78
+ return {
79
+ root: merge('root'),
80
+ items: merge('items'),
81
+ contact: merge('contact'),
82
+ }
83
+ })
84
+ </script>
85
+
86
+ <template>
87
+ <form :class="['space-y-6', slotClasses.root]" @submit.prevent="handleSubmit">
88
+ <h3 class="text-lg font-semibold text-highlighted">Request for Quote</h3>
89
+
90
+ <!-- Line items -->
91
+ <div :class="['space-y-4', slotClasses.items]">
92
+ <div
93
+ v-for="(item, i) in items"
94
+ :key="i"
95
+ class="rounded-lg bg-elevated p-4 ring ring-default relative"
96
+ >
97
+ <UButton
98
+ v-if="items.length > 1"
99
+ icon="i-heroicons-x-mark-20-solid"
100
+ variant="ghost"
101
+ color="error"
102
+ size="xs"
103
+ class="absolute top-2 end-2"
104
+ @click="removeItem(i)"
105
+ />
106
+
107
+ <div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
108
+ <UFormField label="Product">
109
+ <UInput v-model="item.productName" :placeholder="item.productId || 'Product ID'" disabled size="sm" />
110
+ </UFormField>
111
+ <UFormField label="Quantity">
112
+ <UInput v-model.number="item.quantity" type="number" min="1" size="sm" required />
113
+ </UFormField>
114
+ <UFormField label="Target Price (optional)">
115
+ <UInput v-model.number="item.targetPrice" type="number" min="0" step="0.01" placeholder="Your ideal price" size="sm" />
116
+ </UFormField>
117
+ </div>
118
+ <UFormField label="Note" class="mt-2">
119
+ <UInput v-model="item.note" placeholder="Special requirements…" size="sm" />
120
+ </UFormField>
121
+ </div>
122
+
123
+ <UButton variant="outline" size="sm" @click="addItem" icon="i-heroicons-plus-20-solid">
124
+ Add Item
125
+ </UButton>
126
+ </div>
127
+
128
+ <USeparator />
129
+
130
+ <!-- Contact info -->
131
+ <div :class="['grid grid-cols-1 sm:grid-cols-2 gap-4', slotClasses.contact]">
132
+ <UFormField label="Company Name">
133
+ <UInput v-model="companyName" placeholder="Your company" />
134
+ </UFormField>
135
+ <UFormField label="Contact Email" required>
136
+ <UInput v-model="contactEmail" type="email" placeholder="buyer@company.com" required />
137
+ </UFormField>
138
+ </div>
139
+
140
+ <UFormField label="Additional Notes">
141
+ <UTextarea v-model="note" placeholder="Any additional requirements, delivery timeline, etc." rows="3" />
142
+ </UFormField>
143
+
144
+ <UButton type="submit" block size="lg" color="primary" :loading="loading">
145
+ Submit Quote Request
146
+ </UButton>
147
+ </form>
148
+ </template>
@@ -0,0 +1,96 @@
1
+ <script setup lang="ts">
2
+ import type { WishlistItem, Product } from '@commercejs/types'
3
+
4
+ /**
5
+ * CWishlistGrid — Displays wishlist items in a responsive grid.
6
+ * Composes CProductCard for each item with remove + add-to-cart actions.
7
+ */
8
+
9
+ export interface WishlistGridProps {
10
+ items: WishlistItem[]
11
+ /** Number of grid columns (2–6) */
12
+ columns?: 2 | 3 | 4 | 5 | 6
13
+ /** Whether any action is loading */
14
+ loading?: boolean
15
+ /** Per-instance theme overrides */
16
+ ui?: Partial<{
17
+ root: any
18
+ item: any
19
+ actions: any
20
+ }>
21
+ }
22
+
23
+ const props = withDefaults(defineProps<WishlistGridProps>(), {
24
+ columns: 4,
25
+ loading: false,
26
+ })
27
+
28
+ const emit = defineEmits<{
29
+ 'remove': [itemId: string]
30
+ 'add-to-cart': [product: Product]
31
+ }>()
32
+
33
+ const colClass = computed(() => {
34
+ const map: Record<number, string> = {
35
+ 2: 'grid-cols-1 sm:grid-cols-2',
36
+ 3: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
37
+ 4: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4',
38
+ 5: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5',
39
+ 6: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6',
40
+ }
41
+ return map[props.columns] || map[4]
42
+ })
43
+
44
+ // Resolve theme
45
+ const appConfig = useAppConfig()
46
+ const theme = computed(() => (appConfig.ui as any)?.wishlistGrid ?? {})
47
+
48
+ const slotClasses = computed(() => {
49
+ const base = theme.value?.slots ?? {}
50
+ const merge = (slot: string) => [base[slot], props.ui?.[slot as keyof typeof props.ui]]
51
+ return {
52
+ root: merge('root'),
53
+ item: merge('item'),
54
+ actions: merge('actions'),
55
+ }
56
+ })
57
+ </script>
58
+
59
+ <template>
60
+ <div v-if="items.length === 0">
61
+ <CEmptyState
62
+ icon="i-heroicons-heart"
63
+ title="Your wishlist is empty"
64
+ description="Save your favorite products to find them later"
65
+ action-label="Start Shopping"
66
+ action-to="/products"
67
+ />
68
+ </div>
69
+
70
+ <div v-else :class="['grid gap-4', colClass, slotClasses.root]">
71
+ <div v-for="item in items" :key="item.id" :class="['relative group', slotClasses.item]">
72
+ <CProductCard :product="item.product" />
73
+
74
+ <!-- Wishlist-specific actions overlay -->
75
+ <div :class="['absolute bottom-0 inset-x-0 bg-gradient-to-t from-black/60 to-transparent p-3 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity', slotClasses.actions]">
76
+ <UButton
77
+ size="xs"
78
+ color="primary"
79
+ class="flex-1"
80
+ :loading="loading"
81
+ @click="emit('add-to-cart', item.product)"
82
+ >
83
+ <UIcon name="i-heroicons-shopping-cart-20-solid" class="me-1" />
84
+ Add to Cart
85
+ </UButton>
86
+ <UButton
87
+ icon="i-heroicons-trash-20-solid"
88
+ size="xs"
89
+ variant="ghost"
90
+ color="error"
91
+ @click="emit('remove', item.id)"
92
+ />
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </template>