@pradip1995/theme-sahsha 3.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 (65) hide show
  1. package/README.md +29 -0
  2. package/assets/hero-desktop.svg +10 -0
  3. package/assets/hero-mobile.svg +9 -0
  4. package/assets/logo.svg +3 -0
  5. package/package.json +60 -0
  6. package/src/blocks/home/Features/index.tsx +87 -0
  7. package/src/blocks/home/Hero/index.tsx +98 -0
  8. package/src/blocks/home/LovedByMoms/bestsellers-carousel.tsx +1 -0
  9. package/src/blocks/home/LovedByMoms/index.tsx +43 -0
  10. package/src/blocks/home/LovedByMoms/loved-by-moms-section.tsx +46 -0
  11. package/src/blocks/home/NewArrivals/index.tsx +91 -0
  12. package/src/blocks/home/PromotionalBanners/index.tsx +81 -0
  13. package/src/blocks/home/ShopByAge/collections-showcase-client.tsx +131 -0
  14. package/src/blocks/home/ShopByAge/collections-showcase-types.ts +4 -0
  15. package/src/blocks/home/ShopByAge/index.tsx +168 -0
  16. package/src/blocks/home/ShopByCategory/index.tsx +111 -0
  17. package/src/blocks/home/Testimonials/index.tsx +25 -0
  18. package/src/blocks/home/Testimonials/reviews-scroll.tsx +122 -0
  19. package/src/blocks/home/Testimonials/testimonials-client.tsx +127 -0
  20. package/src/blocks/home/WhyChooseUs/index.tsx +39 -0
  21. package/src/components/product-carousel.tsx +79 -0
  22. package/src/layouts/MainLayoutShell.tsx +14 -0
  23. package/src/primitives/Button.tsx +31 -0
  24. package/src/primitives/Card.tsx +32 -0
  25. package/src/primitives/index.ts +2 -0
  26. package/src/slots/account/ForgotPassword/index.tsx +1 -0
  27. package/src/slots/account/GoogleLogin/index.tsx +28 -0
  28. package/src/slots/account/Login/index.tsx +1 -0
  29. package/src/slots/account/LoginTemplate/index.tsx +12 -0
  30. package/src/slots/account/LoginTemplate/login-template-client.tsx +83 -0
  31. package/src/slots/account/Register/index.tsx +1 -0
  32. package/src/slots/cart/CartItem/index.tsx +11 -0
  33. package/src/slots/cart/CartSummary/index.tsx +8 -0
  34. package/src/slots/checkout/CheckoutForm/index.tsx +1 -0
  35. package/src/slots/checkout/CheckoutSummary/index.tsx +1 -0
  36. package/src/slots/layout/Footer/index.tsx +95 -0
  37. package/src/slots/layout/Nav/index.tsx +50 -0
  38. package/src/slots/layout/Nav/nav-categories-dropdown.tsx +74 -0
  39. package/src/slots/layout/Nav/nav-collections-dropdown.tsx +106 -0
  40. package/src/slots/layout/Nav/nav-header-content.tsx +165 -0
  41. package/src/slots/layout/Nav/nav-header-shell.tsx +47 -0
  42. package/src/slots/layout/Nav/nav-link-luxury.tsx +15 -0
  43. package/src/slots/layout/PromoBar/index.tsx +9 -0
  44. package/src/slots/layout/PromoBar/promo-bar-content.tsx +118 -0
  45. package/src/slots/order/OrderDetails/index.tsx +12 -0
  46. package/src/slots/product/ProductActions/ProductCTASection.tsx +232 -0
  47. package/src/slots/product/ProductActions/ProductDetailsSection.tsx +200 -0
  48. package/src/slots/product/ProductActions/ProductFeaturePanel.tsx +150 -0
  49. package/src/slots/product/ProductActions/ProductHighlightsSection.tsx +112 -0
  50. package/src/slots/product/ProductActions/ProductOptionsSection.tsx +215 -0
  51. package/src/slots/product/ProductActions/ProductPriceSection.tsx +53 -0
  52. package/src/slots/product/ProductActions/ProductTrustSection.tsx +84 -0
  53. package/src/slots/product/ProductActions/SizeChartPanel.tsx +93 -0
  54. package/src/slots/product/ProductActions/index.tsx +156 -0
  55. package/src/slots/product/ProductActions/product-metadata-fields.ts +503 -0
  56. package/src/slots/product/ProductActions/size-chart-data.ts +108 -0
  57. package/src/slots/product/ProductCard/index.tsx +258 -0
  58. package/src/slots/product/ProductInfo/index.tsx +35 -0
  59. package/src/templates/CollectionsPage/index.tsx +72 -0
  60. package/src/templates/StorePage/index.tsx +134 -0
  61. package/src/tokens/colors.ts +21 -0
  62. package/src/tokens/fonts.ts +16 -0
  63. package/src/tokens/index.ts +3 -0
  64. package/src/tokens/spacing.ts +9 -0
  65. package/src/tokens/theme.css +12754 -0
@@ -0,0 +1,112 @@
1
+ "use client"
2
+
3
+ import { clx } from "@medusajs/ui"
4
+ import { HttpTypes } from "@medusajs/types"
5
+ import { formatMetadataValue, PRODUCT_METADATA_FIELDS } from "./product-metadata-fields"
6
+
7
+ function getMetadataDisplay(
8
+ metadata: Record<string, unknown> | null | undefined,
9
+ key: string
10
+ ) {
11
+ const field = PRODUCT_METADATA_FIELDS.find((item) => item.key === key)
12
+ if (!field || !metadata) {
13
+ return null
14
+ }
15
+
16
+ return formatMetadataValue(metadata[key], field)
17
+ }
18
+
19
+ export function ProductHighlightsSection({ product }: { product: HttpTypes.StoreProduct }) {
20
+ return (
21
+ <div className="product-highlights flex flex-col gap-6 py-8 border-t border-[#e5e5e5]">
22
+ <div className="flex items-center gap-3">
23
+ <div className="product-highlights__accent" aria-hidden />
24
+ <h3 className="product-highlights__title">Product highlights</h3>
25
+ </div>
26
+
27
+ <div className="grid grid-cols-2 gap-3 sm:gap-4 mt-2">
28
+ {(() => {
29
+ // Extract unique colors from variants/options instead of metadata
30
+ const colorOpt = product.options?.find((opt: HttpTypes.StoreProductOption) =>
31
+ opt.title?.toLowerCase().includes("color") ||
32
+ opt.title?.toLowerCase().includes("colour")
33
+ )
34
+ const variantColors =
35
+ colorOpt?.values?.map((v: { value?: string }) => v.value).filter(Boolean) || []
36
+ const uniqueColors = Array.from(new Set(variantColors))
37
+ const colorValue = uniqueColors.length > 0 ? uniqueColors.join(', ') : null
38
+
39
+ const highlights = [
40
+ { id: 'style', label: "Style", value: getMetadataDisplay(product.metadata, 'style'), icon: 'M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5' },
41
+ { id: 'fabric', label: "Fabric", value: getMetadataDisplay(product.metadata, 'fabric'), icon: 'M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5' },
42
+ { id: 'top_pattern', label: "Pattern", value: getMetadataDisplay(product.metadata, 'top_pattern'), icon: 'M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z' },
43
+ { id: 'top_length', label: "Length", value: getMetadataDisplay(product.metadata, 'top_length'), icon: 'M12 19V5M5 12l7-7 7 7' },
44
+ { id: 'color', label: "Available Colors", value: colorValue, icon: 'M12 2.25l-7.5 12.13a6.75 6.75 0 1015 0L12 2.25z' },
45
+ ].filter(h => h.value)
46
+
47
+ return highlights.map((highlight) => (
48
+ <div
49
+ key={highlight.id}
50
+ className={clx(
51
+ "product-highlights__card group flex items-center gap-3 sm:gap-4 p-3 sm:p-4",
52
+ highlight.id === 'color' && "col-span-2"
53
+ )}
54
+ >
55
+ <div className="flex-shrink-0 w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-surface-muted flex items-center justify-center group-hover:bg-purple-50 transition-all duration-300 shadow-inner overflow-hidden">
56
+ {highlight.id === 'color' ? (
57
+ <svg width="28" height="28" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" className="transform group-hover:scale-110 transition-transform duration-300">
58
+ {/* Palette Body */}
59
+ <path d="M75 55c0 15-12 27-27 27S21 70 21 55s12-27 27-27 27 12 27 27z" fill="#E8B088" stroke="#333" strokeWidth="3" />
60
+
61
+ {/* Color Dots */}
62
+ <circle cx="40" cy="45" r="5" fill="#FF4D4D" stroke="#333" strokeWidth="1.5" /> {/* Red */}
63
+ <circle cx="56" cy="46" r="5" fill="#2ECC71" stroke="#333" strokeWidth="1.5" /> {/* Green */}
64
+ <circle cx="42" cy="62" r="5" fill="#3498DB" stroke="#333" strokeWidth="1.5" /> {/* Blue */}
65
+ <circle cx="58" cy="64" r="5" fill="#F06292" stroke="#333" strokeWidth="1.5" /> {/* Pink */}
66
+
67
+ {/* Brush */}
68
+ <g transform="rotate(25 70 40)">
69
+ <rect x="68" y="15" width="6" height="35" rx="3" fill="#F39C12" stroke="#333" strokeWidth="2" /> {/* Handle */}
70
+ <path d="M68 12c0-4 3-8 3-8s3 4 3 8H68z" fill="#AED6F1" stroke="#333" strokeWidth="2" /> {/* Tip */}
71
+ <rect x="68" y="45" width="6" height="4" fill="#BDC3C7" stroke="#333" strokeWidth="1.5" /> {/* Ferrule */}
72
+ </g>
73
+ </svg>
74
+ ) : (
75
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" className="w-[18px] h-[18px] sm:w-5 sm:h-5 text-brand-accent">
76
+ <path d={highlight.icon} />
77
+ </svg>
78
+ )}
79
+ </div>
80
+ <div className="flex flex-col min-w-0 flex-1">
81
+ <span className="text-[9px] sm:text-[10px] font-black text-gray-800 uppercase tracking-widest mb-0.5 sm:mb-1">{highlight.label}</span>
82
+ <div className="flex items-center gap-2">
83
+ {highlight.id === 'color' ? (
84
+ <div className="flex flex-wrap gap-x-4 gap-y-1.5">
85
+ {String(highlight.value || '').split(/[,\s]+/).map((color, i) => {
86
+ const trimmedColor = color.trim().toLowerCase()
87
+ if (!trimmedColor) return null
88
+ return (
89
+ <div key={i} className="flex items-center gap-2">
90
+ <div
91
+ className="w-3.5 h-3.5 rounded-full border border-gray-100 shadow-sm"
92
+ style={{ backgroundColor: trimmedColor }}
93
+ />
94
+ <span className="text-sm text-gray-800 font-semibold capitalize leading-none tracking-tight">{trimmedColor}</span>
95
+ </div>
96
+ )
97
+ })}
98
+ </div>
99
+ ) : (
100
+ <span className="text-xs sm:text-sm text-gray-800 font-semibold tracking-tight truncate">
101
+ {String(highlight.value || '')}
102
+ </span>
103
+ )}
104
+ </div>
105
+ </div>
106
+ </div>
107
+ ))
108
+ })()}
109
+ </div>
110
+ </div>
111
+ )
112
+ }
@@ -0,0 +1,215 @@
1
+ "use client"
2
+
3
+ import Image from "next/image"
4
+ import { HttpTypes } from "@medusajs/types"
5
+ import OptionSelect from "@modules/products/components/product-actions/option-select"
6
+ import type { ProductOptions } from "@core/domain/product/variant-selection"
7
+
8
+ function getUniqueOptionValues(option: HttpTypes.StoreProductOption): string[] {
9
+ const values =
10
+ option.values?.map((v) => v.value).filter((v): v is string => Boolean(v)) ?? []
11
+ return Array.from(new Set(values))
12
+ }
13
+
14
+ function shouldShowOptionSection(
15
+ option: HttpTypes.StoreProductOption | undefined,
16
+ colorVariants: HttpTypes.StoreProductVariant[] = []
17
+ ): boolean {
18
+ if (!option) return false
19
+
20
+ const isColor =
21
+ option.title?.toLowerCase().includes("color") ||
22
+ option.title?.toLowerCase().includes("colour")
23
+
24
+ if (isColor && colorVariants.length > 1) {
25
+ return true
26
+ }
27
+
28
+ const uniqueValues = getUniqueOptionValues(option)
29
+ if (uniqueValues.length <= 1) {
30
+ return false
31
+ }
32
+
33
+ const title = (option.title || "").toLowerCase()
34
+ if (title.includes("default")) {
35
+ return false
36
+ }
37
+
38
+ return true
39
+ }
40
+
41
+ type ProductOptionsSectionProps = {
42
+ product: HttpTypes.StoreProduct
43
+ disabled?: boolean
44
+ isAdding: boolean
45
+ options: ProductOptions
46
+ setOptionValue: (optionId: string, value: string) => void
47
+ setColorValue: (optionId: string, value: string) => void
48
+ validationErrors: Record<string, string>
49
+ colorOption?: HttpTypes.StoreProductOption
50
+ sizeOption?: HttpTypes.StoreProductOption
51
+ otherOptions: HttpTypes.StoreProductOption[]
52
+ colorVariants: HttpTypes.StoreProductVariant[]
53
+ selectedColorValue?: string
54
+ onOpenSizeChart: () => void
55
+ }
56
+
57
+ export function ProductOptionsSection({
58
+ product,
59
+ disabled,
60
+ isAdding,
61
+ options,
62
+ setOptionValue,
63
+ setColorValue,
64
+ validationErrors,
65
+ colorOption,
66
+ sizeOption,
67
+ otherOptions,
68
+ colorVariants,
69
+ selectedColorValue,
70
+ onOpenSizeChart,
71
+ }: ProductOptionsSectionProps) {
72
+ return (
73
+ <>
74
+ {shouldShowOptionSection(sizeOption) && sizeOption && (
75
+ <div className="flex flex-col gap-3">
76
+ <div className="flex items-center justify-between">
77
+ <div className="flex items-center gap-2">
78
+ <h3 className="font-display text-[11px] font-semibold text-heading uppercase tracking-[var(--letter-spacing-nav)]">
79
+ Select Size
80
+ </h3>
81
+ {validationErrors[sizeOption.id] && (
82
+ <span className="text-[10px] text-red-600 font-black uppercase tracking-wider animate-pulse border-l-2 border-red-600 pl-2">
83
+ Please Select {String(sizeOption.title || "")}
84
+ </span>
85
+ )}
86
+ </div>
87
+ <button
88
+ type="button"
89
+ onClick={onOpenSizeChart}
90
+ className="product-size-chart-link flex items-center gap-1.5 text-xs sm:text-sm font-medium hover:opacity-80"
91
+ >
92
+ <Image
93
+ src="/Size Chart.svg"
94
+ alt=""
95
+ width={16}
96
+ height={16}
97
+ className="product-size-chart-link__icon"
98
+ aria-hidden
99
+ />
100
+ Size Chart
101
+ </button>
102
+ </div>
103
+ <OptionSelect
104
+ option={sizeOption}
105
+ current={options[sizeOption.id]}
106
+ updateOption={setOptionValue}
107
+ title={sizeOption.title ?? ""}
108
+ data-testid="product-options"
109
+ disabled={!!disabled || isAdding}
110
+ error={!!validationErrors[sizeOption.id]}
111
+ />
112
+ </div>
113
+ )}
114
+
115
+ {shouldShowOptionSection(colorOption, colorVariants) && colorOption && (
116
+ <div className="product-color-variants flex flex-col gap-3">
117
+ <div className="flex items-center gap-2">
118
+ <h3 className="font-display text-[11px] font-semibold text-heading uppercase tracking-[var(--letter-spacing-nav)]">
119
+ {colorVariants.length > 0
120
+ ? "More Colors"
121
+ : `Select ${colorOption.title}`}
122
+ </h3>
123
+ {validationErrors[colorOption.id] && (
124
+ <span className="text-[10px] text-red-600 font-black uppercase tracking-wider animate-pulse border-l-2 border-red-600 pl-2">
125
+ Please Select {String(colorOption.title || "")}
126
+ </span>
127
+ )}
128
+ </div>
129
+ {colorVariants.length > 0 ? (
130
+ <div className="product-color-variants__list flex gap-2.5 sm:gap-3 overflow-x-auto pb-1">
131
+ {colorVariants.map((variant) => {
132
+ const variantImage =
133
+ (variant.metadata?.image as string) ||
134
+ variant.thumbnail ||
135
+ variant.images?.[0]?.url ||
136
+ product.thumbnail
137
+ const variantColorValue = variant.options?.find(
138
+ (opt: HttpTypes.StoreProductOptionValue) =>
139
+ opt.option_id === colorOption.id
140
+ )?.value
141
+ const isSelected = variantColorValue === selectedColorValue
142
+
143
+ return (
144
+ <button
145
+ key={variant.id}
146
+ type="button"
147
+ className={`product-color-variants__card relative flex-shrink-0 overflow-hidden border ${
148
+ isSelected
149
+ ? "border-brand-accent ring-1 ring-brand-accent"
150
+ : "border-[var(--color-header-border)] hover:border-brand-accent-light"
151
+ }`}
152
+ onClick={() => {
153
+ if (colorOption && variantColorValue) {
154
+ setColorValue(colorOption.id, variantColorValue)
155
+ }
156
+ }}
157
+ title={variantColorValue}
158
+ >
159
+ {variantImage && (
160
+ <Image
161
+ src={variantImage}
162
+ alt={`Color variant: ${variantColorValue || "Unknown"}`}
163
+ fill
164
+ sizes="(max-width: 640px) 72px, 88px"
165
+ className="object-cover"
166
+ />
167
+ )}
168
+ </button>
169
+ )
170
+ })}
171
+ </div>
172
+ ) : (
173
+ <OptionSelect
174
+ option={colorOption}
175
+ current={options[colorOption.id]}
176
+ updateOption={setOptionValue}
177
+ title={colorOption.title ?? ""}
178
+ data-testid="product-options"
179
+ disabled={!!disabled || isAdding}
180
+ error={!!validationErrors[colorOption.id]}
181
+ />
182
+ )}
183
+ </div>
184
+ )}
185
+
186
+ {otherOptions
187
+ .filter((option) => shouldShowOptionSection(option))
188
+ .sort((a, b) => (a.title || "").localeCompare(b.title || ""))
189
+ .map((option) => (
190
+ <div key={option.id} className="flex flex-col gap-3">
191
+ <div className="flex items-center gap-2">
192
+ <h3 className="text-[11px] font-semibold text-heading uppercase tracking-[var(--letter-spacing-nav)]">
193
+ Select {String(option.title || "")}
194
+ </h3>
195
+ {validationErrors[option.id] && (
196
+ <span className="text-[10px] text-red-600 font-black uppercase tracking-wider animate-pulse border-l-2 border-red-600 pl-2">
197
+ Please Select {String(option.title || "")}
198
+ </span>
199
+ )}
200
+ </div>
201
+ <OptionSelect
202
+ option={option}
203
+ current={options[option.id]}
204
+ updateOption={setOptionValue}
205
+ title={option.title ?? ""}
206
+ data-testid="product-options"
207
+ disabled={!!disabled || isAdding}
208
+ error={!!validationErrors[option.id]}
209
+ />
210
+ </div>
211
+ ))}
212
+
213
+ </>
214
+ )
215
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ formatDisplayPrice,
3
+ formatINRPrice,
4
+ type VariantPrice,
5
+ } from "@core/domain/product/pricing"
6
+
7
+ type ProductPriceSectionProps = {
8
+ displayPrice: VariantPrice
9
+ quantityInCart: number
10
+ }
11
+
12
+ export function ProductPriceSection({
13
+ displayPrice,
14
+ quantityInCart,
15
+ }: ProductPriceSectionProps) {
16
+ const originalPrice = displayPrice?.original_price_number
17
+ const currentPrice = displayPrice?.calculated_price_number
18
+ const discountPercentage =
19
+ originalPrice && currentPrice && originalPrice > currentPrice
20
+ ? Math.round(((originalPrice - currentPrice) / originalPrice) * 100)
21
+ : null
22
+
23
+ return (
24
+ <div className="product-price">
25
+ <div className="product-price__row">
26
+ <div className="product-price__main">
27
+ <span className="product-price__current">
28
+ {formatDisplayPrice(displayPrice, currentPrice)}
29
+ </span>
30
+ {originalPrice && currentPrice && originalPrice > currentPrice && (
31
+ <>
32
+ <span className="product-price__mrp">
33
+ MRP {formatINRPrice(originalPrice)}
34
+ </span>
35
+ {discountPercentage !== null && (
36
+ <span className="product-price__discount">
37
+ ({discountPercentage}% OFF)
38
+ </span>
39
+ )}
40
+ </>
41
+ )}
42
+ </div>
43
+
44
+ {quantityInCart > 0 && (
45
+ <span className="product-price__in-bag">
46
+ {quantityInCart} in bag
47
+ </span>
48
+ )}
49
+ </div>
50
+ <p className="product-price__tax">inclusive of all taxes</p>
51
+ </div>
52
+ )
53
+ }
@@ -0,0 +1,84 @@
1
+ "use client"
2
+
3
+ import Image from "next/image"
4
+ import PincodeChecker from "@modules/cart/components/pincode-checker"
5
+
6
+ type ProductTrustSectionProps = {
7
+ selectedVariantId?: string
8
+ onOpenFeature: (feature: string) => void
9
+ }
10
+
11
+ export function ProductTrustSection({ selectedVariantId, onOpenFeature }: ProductTrustSectionProps) {
12
+ return (
13
+ <>
14
+ <div className="product-trust-bar flex items-center justify-between py-3 px-4 bg-[#f8f4ef] border border-[#e5e5e5] mt-2 mb-2">
15
+ <div className="flex items-center gap-3 sm:gap-4">
16
+ <Image src="/Item.svg" alt="Mastercard" width={40} height={28} className="h-6 sm:h-7 w-auto grayscale-[0.2]" />
17
+ <Image src="/Item (1).svg" alt="Amex" width={40} height={28} className="h-6 sm:h-7 w-auto grayscale-[0.2]" />
18
+ <Image src="/Item (2).svg" alt="Diners" width={40} height={28} className="h-7 sm:h-7 w-auto grayscale-[0.2]" />
19
+ <Image src="/visa.svg" alt="Visa" width={40} height={28} className="h-5 sm:h-6 w-auto grayscale-[0.2]" />
20
+ </div>
21
+ <div className="flex items-center gap-1.5 sm:gap-2 ml-2 sm:ml-0">
22
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#156229" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
23
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
24
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
25
+ </svg>
26
+ <span className="text-[9px] sm:text-[11px] font-bold text-gray-700 uppercase tracking-wider whitespace-nowrap">Secure Payments</span>
27
+ </div>
28
+ </div>
29
+
30
+ {/* Pincode Checker Section */}
31
+ <div className="mt-0.5 sm:-mt-1.5 mb-0">
32
+ <PincodeChecker variantId={selectedVariantId} />
33
+ </div>
34
+
35
+ {/* Service Features Section */}
36
+ <div className="product-trust-features flex flex-row overflow-x-auto no-scrollbar py-4 border-t border-[#e5e5e5] gap-4 min-[500px]:gap-0 min-[500px]:justify-between min-[500px]:overflow-visible">
37
+ {/* 7 Days Easy Return */}
38
+ <div className="flex flex-col items-center gap-2 flex-shrink-0 min-w-[120px] min-[500px]:flex-1 min-[500px]:min-w-0 group">
39
+ <div className="w-8 h-8 flex items-center justify-center">
40
+ <Image src="/Return.svg" alt="Return" width={28} height={26} />
41
+ </div>
42
+ <div className="flex items-center gap-1.5 cursor-pointer hover:opacity-80 transition-opacity" onClick={() => onOpenFeature('return')}>
43
+ <span className="text-xs text-gray-800 font-medium text-center whitespace-nowrap leading-none">7 Days Easy Return</span>
44
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" className="product-trust-features__chevron">
45
+ <polyline points="9 18 15 12 9 6"></polyline>
46
+ </svg>
47
+ </div>
48
+ </div>
49
+
50
+ {/* Separator */}
51
+ <div className="hidden min-[500px]:block w-px h-12 bg-gray-200 flex-shrink-0"></div>
52
+
53
+ {/* Fast Delivery */}
54
+ <div className="flex flex-col items-center gap-2 flex-shrink-0 min-w-[120px] min-[500px]:flex-1 min-[500px]:min-w-0 group">
55
+ <div className="w-8 h-8 flex items-center justify-center">
56
+ <Image src="/delivery.svg" alt="Delivery" width={30} height={18} />
57
+ </div>
58
+ <div className="flex items-center gap-1.5 cursor-pointer hover:opacity-80 transition-opacity" onClick={() => onOpenFeature('delivery')}>
59
+ <span className="text-xs text-gray-800 font-medium text-center whitespace-nowrap leading-none">Fast Delivery</span>
60
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" className="product-trust-features__chevron">
61
+ <polyline points="9 18 15 12 9 6"></polyline>
62
+ </svg>
63
+ </div>
64
+ </div>
65
+
66
+ {/* Separator */}
67
+ <div className="hidden min-[500px]:block w-px h-12 bg-gray-200 flex-shrink-0"></div>
68
+
69
+ {/* Cash On Delivery Available */}
70
+ <div className="flex flex-col items-center gap-2 flex-shrink-0 min-w-[120px] min-[500px]:flex-1 min-[500px]:min-w-0 group">
71
+ <div className="w-8 h-8 flex items-center justify-center">
72
+ <Image src="/Cod.svg" alt="COD" width={30} height={18} />
73
+ </div>
74
+ <div className="flex items-center gap-1.5 cursor-pointer hover:opacity-80 transition-opacity" onClick={() => onOpenFeature('cod')}>
75
+ <span className="text-xs text-gray-800 font-medium text-center whitespace-nowrap leading-none">Cash On Delivery Available</span>
76
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" className="product-trust-features__chevron">
77
+ <polyline points="9 18 15 12 9 6"></polyline>
78
+ </svg>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </>
83
+ )
84
+ }
@@ -0,0 +1,93 @@
1
+ import {
2
+ SIZE_CHART_MEASURE_TIPS,
3
+ SIZE_CHART_SECTIONS,
4
+ type SizeChartSection,
5
+ } from "./size-chart-data"
6
+
7
+ function SizeChartTable({ section }: { section: SizeChartSection }) {
8
+ return (
9
+ <div className="size-chart__table-wrap">
10
+ <table className="size-chart__table">
11
+ <thead>
12
+ <tr>
13
+ {section.columns.map((column) => (
14
+ <th
15
+ key={column.key}
16
+ className={column.align === "center" ? "text-center" : undefined}
17
+ >
18
+ {column.label}
19
+ </th>
20
+ ))}
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ {section.rows.map((row, index) => (
25
+ <tr key={`${section.id}-${index}`}>
26
+ {section.columns.map((column) => (
27
+ <td
28
+ key={column.key}
29
+ className={column.align === "center" ? "text-center" : undefined}
30
+ >
31
+ {row[column.key]}
32
+ </td>
33
+ ))}
34
+ </tr>
35
+ ))}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ )
40
+ }
41
+
42
+ export function SizeChartPanel() {
43
+ return (
44
+ <div className="size-chart">
45
+ <div className="size-chart__tip">
46
+ <div className="size-chart__tip-icon" aria-hidden>
47
+ 💡
48
+ </div>
49
+ <div>
50
+ <h4 className="size-chart__tip-title">Pro Tip</h4>
51
+ <p className="size-chart__tip-text">
52
+ Measure yourself in inches before ordering. If you are between sizes,
53
+ choose the larger size for a comfortable ethnic fit. For saree blouses,
54
+ match your bust measurement to the blouse size chart.
55
+ </p>
56
+ </div>
57
+ </div>
58
+
59
+ <div className="size-chart__measure">
60
+ <h4 className="size-chart__section-title">How to measure</h4>
61
+ <ul className="size-chart__measure-list">
62
+ {SIZE_CHART_MEASURE_TIPS.map((tip) => (
63
+ <li key={tip.title}>
64
+ <strong>{tip.title}:</strong> {tip.text}
65
+ </li>
66
+ ))}
67
+ </ul>
68
+ </div>
69
+
70
+ <div className="size-chart__sections">
71
+ {SIZE_CHART_SECTIONS.map((section) => (
72
+ <section key={section.id} className="size-chart__section">
73
+ <div className="size-chart__section-head">
74
+ <div className="size-chart__section-icon" aria-hidden>
75
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
76
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
77
+ <circle cx="12" cy="7" r="4" />
78
+ </svg>
79
+ </div>
80
+ <div>
81
+ <h4 className="size-chart__section-title">{section.title}</h4>
82
+ {section.subtitle && (
83
+ <p className="size-chart__section-subtitle">{section.subtitle}</p>
84
+ )}
85
+ </div>
86
+ </div>
87
+ <SizeChartTable section={section} />
88
+ </section>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ )
93
+ }