medusa-ui-common 2.0.1 → 3.0.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/package.json +14 -3
- package/src/common/components/breadcrumb/index.tsx +2 -43
- package/src/common/components/cart-totals/index.tsx +39 -44
- package/src/common/components/checkbox/index.tsx +17 -9
- package/src/common/components/delete-button/index.tsx +6 -6
- package/src/common/components/discount-code/index.tsx +11 -11
- package/src/common/components/filter-checkbox-group/index.tsx +2 -134
- package/src/common/components/input/index.tsx +18 -6
- package/src/common/components/login-popup/index.tsx +2 -78
- package/src/common/components/modal/index.tsx +2 -2
- package/src/common/components/native-select/index.tsx +1 -1
- package/src/common/components/processing-overlay/index.tsx +4 -4
- package/src/common/components/side-panel/index.tsx +2 -65
- package/src/common/components/submit-button/index.tsx +17 -14
- package/src/common/components/themed-button/index.tsx +63 -0
- package/src/constants/payments.tsx +2 -31
- package/src/context/button-theme-context.tsx +131 -0
- package/src/context/site-theme-context.tsx +89 -0
- package/src/context/storefront-theme-classes-context.tsx +84 -0
- package/src/index.ts +8 -0
- package/src/page-compose/types.ts +31 -0
- package/src/skeletons/templates/skeleton-order-confirmed/index.tsx +1 -1
- package/src/storefront-providers.tsx +103 -3
- package/src/util/button-css-vars.ts +6 -0
- package/src/util/checkout-dom.ts +2 -65
- package/src/util/returns.ts +3 -72
- package/src/util/storefront-theme.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "medusa-ui-common",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Shared storefront UI primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"lucide-react": "*",
|
|
23
23
|
"medusa-contact-logic-plugin": "^2.0.0",
|
|
24
24
|
"medusa-reviews-logic": "^2.0.0",
|
|
25
|
-
"medusa-storefront-analytics": "^1.
|
|
26
|
-
"medusa-storefront-data": "^2.
|
|
25
|
+
"medusa-storefront-analytics": "^1.0.0",
|
|
26
|
+
"medusa-storefront-data": "^2.5.3",
|
|
27
27
|
"medusa-storefront-hooks": "^1.0.0",
|
|
28
28
|
"medusa-wishlist-logic": "^2.0.0",
|
|
29
|
+
"medusa-storefront-theme-base": "^3.0.0",
|
|
29
30
|
"next": ">=14.0.0",
|
|
30
31
|
"react": "^18.0.0 || ^19.0.0",
|
|
31
32
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
@@ -86,6 +87,11 @@
|
|
|
86
87
|
"types": "./src/constants/*",
|
|
87
88
|
"import": "./src/constants/*",
|
|
88
89
|
"default": "./src/constants/*"
|
|
90
|
+
},
|
|
91
|
+
"./page-compose/types": {
|
|
92
|
+
"types": "./src/page-compose/types.ts",
|
|
93
|
+
"import": "./src/page-compose/types.ts",
|
|
94
|
+
"default": "./src/page-compose/types.ts"
|
|
89
95
|
}
|
|
90
96
|
},
|
|
91
97
|
"typesVersions": {
|
|
@@ -96,6 +102,11 @@
|
|
|
96
102
|
}
|
|
97
103
|
},
|
|
98
104
|
"devDependencies": {
|
|
105
|
+
"next": "^15.0.0",
|
|
106
|
+
"medusa-storefront-analytics": "workspace:*",
|
|
107
|
+
"medusa-storefront-core": "workspace:*",
|
|
108
|
+
"medusa-storefront-data": "workspace:*",
|
|
109
|
+
"medusa-storefront-theme-base": "workspace:*",
|
|
99
110
|
"@headlessui/react": "^2.2.0",
|
|
100
111
|
"@medusajs/icons": "^2.0.0",
|
|
101
112
|
"@medusajs/ui": "^4.0.0",
|
|
@@ -1,43 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type BreadcrumbProps = {
|
|
5
|
-
product: HttpTypes.StoreProduct
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default function Breadcrumb({ product }: BreadcrumbProps) {
|
|
9
|
-
const primaryCategory = product.categories?.[0]
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<nav className="text-xs sm:text-sm text-gray-800 mb-3 sm:mb-4 overflow-x-auto font-medium">
|
|
13
|
-
<div className="flex items-center whitespace-nowrap">
|
|
14
|
-
<LocalizedClientLink href="/" className="hover:text-gray-900 transition-colors">
|
|
15
|
-
Home
|
|
16
|
-
</LocalizedClientLink>
|
|
17
|
-
<span className="mx-2">/</span>
|
|
18
|
-
<LocalizedClientLink href="/store" className="hover:text-gray-900 transition-colors">
|
|
19
|
-
Shop
|
|
20
|
-
</LocalizedClientLink>
|
|
21
|
-
{primaryCategory && (
|
|
22
|
-
<>
|
|
23
|
-
<span className="mx-2">/</span>
|
|
24
|
-
<LocalizedClientLink
|
|
25
|
-
href={`/store?category=${primaryCategory.id}`}
|
|
26
|
-
className="hover:text-gray-900 transition-colors"
|
|
27
|
-
>
|
|
28
|
-
{primaryCategory.name}
|
|
29
|
-
</LocalizedClientLink>
|
|
30
|
-
</>
|
|
31
|
-
)}
|
|
32
|
-
<span className="mx-2">/</span>
|
|
33
|
-
<span className="text-gray-900 truncate max-w-[150px] sm:max-w-[300px] md:max-w-[400px] inline-block align-bottom" title={product.title ?? ""}>
|
|
34
|
-
{product.title}
|
|
35
|
-
</span>
|
|
36
|
-
</div>
|
|
37
|
-
</nav>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
/** @deprecated Import from `medusa-ui-product/products/components/breadcrumb` */
|
|
2
|
+
export { default } from "medusa-ui-product/products/components/breadcrumb"
|
|
@@ -9,6 +9,8 @@ import LocalizedClientLink from "medusa-ui-common/common/components/localized-cl
|
|
|
9
9
|
import { useRouter, usePathname, useSearchParams } from "next/navigation"
|
|
10
10
|
import ProcessingOverlay from "medusa-ui-common/common/components/processing-overlay"
|
|
11
11
|
import { useSiteCompany } from "medusa-storefront-core"
|
|
12
|
+
import { useButtonClassName } from "../../../context/button-theme-context"
|
|
13
|
+
import { ThemedButton } from "../themed-button"
|
|
12
14
|
|
|
13
15
|
type CartTotalsProps = {
|
|
14
16
|
totals: {
|
|
@@ -27,6 +29,8 @@ type CartTotalsProps = {
|
|
|
27
29
|
|
|
28
30
|
const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutStep, customer }) => {
|
|
29
31
|
const company = useSiteCompany()
|
|
32
|
+
const primaryBtn = useButtonClassName("primary", { size: "lg", className: "w-full" })
|
|
33
|
+
const secondaryBtn = useButtonClassName("secondary", { size: "lg", className: "w-full" })
|
|
30
34
|
const router = useRouter()
|
|
31
35
|
const pathname = usePathname()
|
|
32
36
|
const searchParams = useSearchParams()
|
|
@@ -270,13 +274,13 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
return (
|
|
273
|
-
<div className="bg-[
|
|
277
|
+
<div className="bg-[var(--sf-color-background)] rounded-lg p-3 min-[550px]:p-4 sm:p-5 md:p-6 min-[1023px]:p-4 min-[1150px]:p-5 min-[1360px]:p-6 border shadow-sm w-full" style={{ borderColor: '#C0C0C0' }}>
|
|
274
278
|
<ProcessingOverlay isOpen={showOverlay} variant={processingVariant} />
|
|
275
|
-
<h3 className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg font-bold text-
|
|
279
|
+
<h3 className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg font-bold text-[var(--sf-color-text)] mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
|
|
276
280
|
PRICE SUMMARY ({itemCount} {itemCount === 1 ? "ITEM" : "ITEMS"})
|
|
277
281
|
</h3>
|
|
278
282
|
|
|
279
|
-
<div className="flex flex-col gap-y-2 min-[550px]:gap-y-2.5 sm:gap-y-3 min-[1023px]:gap-y-2 min-[1150px]:gap-y-3 min-[1360px]:gap-y-3 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-
|
|
283
|
+
<div className="flex flex-col gap-y-2 min-[550px]:gap-y-2.5 sm:gap-y-3 min-[1023px]:gap-y-2 min-[1150px]:gap-y-3 min-[1360px]:gap-y-3 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-[var(--sf-color-text)]">
|
|
280
284
|
<div className="flex items-center justify-between">
|
|
281
285
|
<span>Total MRP</span>
|
|
282
286
|
<span data-testid="cart-mrp" data-value={totalMRP}>
|
|
@@ -288,7 +292,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
288
292
|
<div className="flex items-center justify-between">
|
|
289
293
|
<span>Discount</span>
|
|
290
294
|
<span
|
|
291
|
-
className="text-
|
|
295
|
+
className="text-[var(--sf-color-text)]"
|
|
292
296
|
data-testid="cart-discount"
|
|
293
297
|
data-value={calculatedDiscount}
|
|
294
298
|
>
|
|
@@ -311,7 +315,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
311
315
|
<span>Shipping</span>
|
|
312
316
|
<span data-testid="cart-shipping" data-value={shippingAmount} className="flex items-center gap-x-2">
|
|
313
317
|
{isAutoSettingShipping && (
|
|
314
|
-
<span className="w-4 h-4 border-2 border-[
|
|
318
|
+
<span className="w-4 h-4 border-2 border-[var(--sf-btn-primary)] border-t-transparent rounded-full animate-spin"></span>
|
|
315
319
|
)}
|
|
316
320
|
{convertToLocale({ amount: shippingAmount, currency_code })}
|
|
317
321
|
</span>
|
|
@@ -320,7 +324,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
320
324
|
|
|
321
325
|
<div className="h-px w-full border-b border-gray-200 my-3 min-[550px]:my-4" />
|
|
322
326
|
|
|
323
|
-
<div className="flex items-center justify-between text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base font-bold text-
|
|
327
|
+
<div className="flex items-center justify-between text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base font-bold text-[var(--sf-color-text)] mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
|
|
324
328
|
<span>Total</span>
|
|
325
329
|
<span
|
|
326
330
|
className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg"
|
|
@@ -344,22 +348,22 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
344
348
|
|
|
345
349
|
{/* No manual continue button needed as we are in one-step mode */}
|
|
346
350
|
{isOnCartPage && (
|
|
347
|
-
<
|
|
351
|
+
<ThemedButton
|
|
352
|
+
variant="primary"
|
|
353
|
+
size="lg"
|
|
354
|
+
fullWidth
|
|
348
355
|
onClick={handleProceedToCheckout}
|
|
349
356
|
disabled={isRedirecting}
|
|
357
|
+
isLoading={isRedirecting}
|
|
350
358
|
data-ga-event="proceed_to_checkout_click"
|
|
351
359
|
data-meta-event="InitiateCheckout"
|
|
352
360
|
data-meta-action="proceed_to_checkout"
|
|
353
361
|
data-ga-label={convertToLocale({ amount: finalTotal, currency_code })}
|
|
354
|
-
className="
|
|
355
|
-
style={{
|
|
356
|
-
backgroundColor: '#8B5AB1',
|
|
357
|
-
borderRadius: '30px'
|
|
358
|
-
}}
|
|
362
|
+
className="mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6"
|
|
359
363
|
data-testid="checkout-button"
|
|
360
364
|
>
|
|
361
365
|
{isRedirecting ? "Loading..." : "Proceed to Checkout"}
|
|
362
|
-
</
|
|
366
|
+
</ThemedButton>
|
|
363
367
|
)}
|
|
364
368
|
{!isOnCartPage && (
|
|
365
369
|
<div className="w-full">
|
|
@@ -376,6 +380,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
376
380
|
return (
|
|
377
381
|
<>
|
|
378
382
|
<button
|
|
383
|
+
type="button"
|
|
379
384
|
onClick={async () => {
|
|
380
385
|
trackPaymentMethodClick("cod")
|
|
381
386
|
setProcessingVariant("order")
|
|
@@ -404,18 +409,14 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
404
409
|
data-meta-event="AddPaymentInfo"
|
|
405
410
|
data-meta-action="select_payment_method"
|
|
406
411
|
data-meta-payment-type="cod"
|
|
407
|
-
className={clx(
|
|
408
|
-
"
|
|
409
|
-
|
|
410
|
-
"hover:bg-purple-50": !isPaymentDisabled,
|
|
411
|
-
"opacity-50 cursor-not-allowed": isPaymentDisabled
|
|
412
|
-
}
|
|
413
|
-
)}
|
|
414
|
-
style={{ borderRadius: '30px' }}
|
|
412
|
+
className={clx(secondaryBtn, {
|
|
413
|
+
"opacity-50 cursor-not-allowed": isPaymentDisabled,
|
|
414
|
+
})}
|
|
415
415
|
>
|
|
416
416
|
Cash on Delivery
|
|
417
417
|
</button>
|
|
418
418
|
<button
|
|
419
|
+
type="button"
|
|
419
420
|
onClick={async () => {
|
|
420
421
|
trackPaymentMethodClick("razorpay")
|
|
421
422
|
setProcessingVariant("payment")
|
|
@@ -469,7 +470,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
469
470
|
contact: paymentPhone || undefined,
|
|
470
471
|
},
|
|
471
472
|
theme: {
|
|
472
|
-
color: "
|
|
473
|
+
color: "var(--sf-btn-primary)"
|
|
473
474
|
},
|
|
474
475
|
modal: {
|
|
475
476
|
ondismiss: () => {
|
|
@@ -498,14 +499,9 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
498
499
|
data-meta-event="AddPaymentInfo"
|
|
499
500
|
data-meta-action="select_payment_method"
|
|
500
501
|
data-meta-payment-type="razorpay"
|
|
501
|
-
className={clx(
|
|
502
|
-
"
|
|
503
|
-
|
|
504
|
-
"hover:opacity-90": !isPaymentDisabled,
|
|
505
|
-
"opacity-50 cursor-not-allowed": isPaymentDisabled
|
|
506
|
-
}
|
|
507
|
-
)}
|
|
508
|
-
style={{ borderRadius: '30px' }}
|
|
502
|
+
className={clx(primaryBtn, {
|
|
503
|
+
"opacity-50 cursor-not-allowed": isPaymentDisabled,
|
|
504
|
+
})}
|
|
509
505
|
>
|
|
510
506
|
Pay with Razorpay
|
|
511
507
|
</button>
|
|
@@ -522,36 +518,35 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
522
518
|
)}
|
|
523
519
|
</div>
|
|
524
520
|
) : activeStep === "address" ? (
|
|
525
|
-
<
|
|
521
|
+
<ThemedButton
|
|
522
|
+
variant="primary"
|
|
523
|
+
size="lg"
|
|
524
|
+
fullWidth
|
|
526
525
|
onClick={() => {
|
|
527
526
|
window.dispatchEvent(new CustomEvent('trigger-checkout-step'))
|
|
528
527
|
}}
|
|
529
|
-
className="
|
|
530
|
-
style={{
|
|
531
|
-
backgroundColor: '#8B5AB1',
|
|
532
|
-
borderRadius: '30px'
|
|
533
|
-
}}
|
|
528
|
+
className="mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6"
|
|
534
529
|
data-testid="checkout-button-address"
|
|
535
530
|
>
|
|
536
531
|
Continue to delivery
|
|
537
|
-
</
|
|
532
|
+
</ThemedButton>
|
|
538
533
|
) : (
|
|
539
|
-
<
|
|
534
|
+
<ThemedButton
|
|
535
|
+
variant="primary"
|
|
536
|
+
size="lg"
|
|
537
|
+
fullWidth
|
|
540
538
|
onClick={handleProceedToCheckout}
|
|
541
539
|
disabled={isRedirecting}
|
|
540
|
+
isLoading={isRedirecting}
|
|
542
541
|
data-ga-event="proceed_to_checkout_click"
|
|
543
542
|
data-meta-event="InitiateCheckout"
|
|
544
543
|
data-meta-action="proceed_to_checkout"
|
|
545
544
|
data-ga-label={convertToLocale({ amount: finalTotal, currency_code })}
|
|
546
|
-
className="
|
|
547
|
-
style={{
|
|
548
|
-
backgroundColor: '#8B5AB1',
|
|
549
|
-
borderRadius: '30px'
|
|
550
|
-
}}
|
|
545
|
+
className="mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6"
|
|
551
546
|
data-testid="checkout-button"
|
|
552
547
|
>
|
|
553
548
|
{isRedirecting ? "Loading..." : "Proceed to Checkout"}
|
|
554
|
-
</
|
|
549
|
+
</ThemedButton>
|
|
555
550
|
)}
|
|
556
551
|
</div>
|
|
557
552
|
)}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Label } from "@medusajs/ui"
|
|
2
1
|
import React from "react"
|
|
3
2
|
import Color from "color"
|
|
4
3
|
|
|
@@ -43,21 +42,24 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
|
|
|
43
42
|
}
|
|
44
43
|
}, [hex, color])
|
|
45
44
|
|
|
45
|
+
const labelId = React.useId()
|
|
46
|
+
|
|
46
47
|
return (
|
|
47
48
|
<div className="flex items-center space-x-2 px-1 py-0.5">
|
|
48
49
|
<button
|
|
49
50
|
type="button"
|
|
50
51
|
role="checkbox"
|
|
51
52
|
aria-checked={checked}
|
|
53
|
+
aria-labelledby={labelId}
|
|
52
54
|
onClick={onChange}
|
|
53
55
|
name={name}
|
|
54
56
|
data-testid={dataTestId}
|
|
55
57
|
data-ga-event={dataGaEvent}
|
|
56
58
|
data-ga-label={dataGaLabel}
|
|
57
|
-
className="w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-offset-2"
|
|
59
|
+
className="w-5 h-5 shrink-0 rounded-md border-2 flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-offset-2"
|
|
58
60
|
style={checked ? {
|
|
59
|
-
backgroundColor: '
|
|
60
|
-
borderColor: '
|
|
61
|
+
backgroundColor: 'var(--sf-btn-primary)',
|
|
62
|
+
borderColor: 'var(--sf-btn-primary)'
|
|
61
63
|
} : {
|
|
62
64
|
backgroundColor: 'white',
|
|
63
65
|
borderColor: '#C0C0C0'
|
|
@@ -77,11 +79,17 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
|
|
|
77
79
|
</svg>
|
|
78
80
|
)}
|
|
79
81
|
</button>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
size="large"
|
|
82
|
+
<span
|
|
83
|
+
id={labelId}
|
|
84
|
+
role="presentation"
|
|
84
85
|
onClick={onChange}
|
|
86
|
+
onKeyDown={(e) => {
|
|
87
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
88
|
+
e.preventDefault()
|
|
89
|
+
onChange?.()
|
|
90
|
+
}
|
|
91
|
+
}}
|
|
92
|
+
className="txt-medium cursor-pointer flex items-center gap-2 text-[var(--sf-color-text)]"
|
|
85
93
|
>
|
|
86
94
|
{colorHex && (
|
|
87
95
|
<div
|
|
@@ -90,7 +98,7 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
|
|
|
90
98
|
/>
|
|
91
99
|
)}
|
|
92
100
|
{label}
|
|
93
|
-
</
|
|
101
|
+
</span>
|
|
94
102
|
</div>
|
|
95
103
|
)
|
|
96
104
|
}
|
|
@@ -100,7 +100,7 @@ const DeleteButton = ({
|
|
|
100
100
|
{/* Body Content */}
|
|
101
101
|
<div className="p-6 flex gap-6 pr-12">
|
|
102
102
|
{/* Product Thumbnail */}
|
|
103
|
-
<div className="w-24 h-24 sm:w-28 sm:h-28 flex-shrink-0 bg-
|
|
103
|
+
<div className="w-24 h-24 sm:w-28 sm:h-28 flex-shrink-0 bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,var(--sf-color-surface))] rounded-sm overflow-hidden border border-gray-100 shadow-sm">
|
|
104
104
|
{thumbnail ? (
|
|
105
105
|
<Image
|
|
106
106
|
src={thumbnail}
|
|
@@ -110,14 +110,14 @@ const DeleteButton = ({
|
|
|
110
110
|
className="w-full h-full object-cover"
|
|
111
111
|
/>
|
|
112
112
|
) : (
|
|
113
|
-
<div className="w-full h-full flex items-center justify-center text-
|
|
113
|
+
<div className="w-full h-full flex items-center justify-center text-[var(--sf-color-text-muted)] bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,var(--sf-color-surface))] uppercase text-[10px] font-bold">No Image</div>
|
|
114
114
|
)}
|
|
115
115
|
</div>
|
|
116
116
|
|
|
117
117
|
{/* Title and Description */}
|
|
118
118
|
<div className="flex flex-col justify-center">
|
|
119
|
-
<h3 className="text-[17px] font-bold text-
|
|
120
|
-
<p className="text-[15px] text-
|
|
119
|
+
<h3 className="text-[17px] font-bold text-[var(--sf-color-text)] mb-1.5 leading-tight">Move from Bag</h3>
|
|
120
|
+
<p className="text-[15px] text-[var(--sf-color-text-muted)] leading-snug font-medium max-w-[280px]">
|
|
121
121
|
Are you sure you want to move this item from bag?
|
|
122
122
|
</p>
|
|
123
123
|
|
|
@@ -132,7 +132,7 @@ const DeleteButton = ({
|
|
|
132
132
|
<button
|
|
133
133
|
onClick={handleDelete}
|
|
134
134
|
disabled={isDeleting}
|
|
135
|
-
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold text-
|
|
135
|
+
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold text-[var(--sf-color-text-muted)] tracking-wider hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)] transition-colors uppercase"
|
|
136
136
|
>
|
|
137
137
|
{isDeleting ? <Spinner className="animate-spin" /> : "REMOVE"}
|
|
138
138
|
</button>
|
|
@@ -143,7 +143,7 @@ const DeleteButton = ({
|
|
|
143
143
|
<button
|
|
144
144
|
onClick={handleMoveToWishlist}
|
|
145
145
|
disabled={wishlistLoading}
|
|
146
|
-
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold tracking-wider hover:bg-
|
|
146
|
+
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold tracking-wider hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)] transition-colors uppercase"
|
|
147
147
|
style={{ color: '#D25C78' }} // Pinkish color from reference
|
|
148
148
|
>
|
|
149
149
|
{wishlistLoading ? <Spinner className="animate-spin" /> : "MOVE TO WISHLIST"}
|
|
@@ -71,7 +71,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
71
71
|
style={{ width: '480px', maxWidth: '100%' }}
|
|
72
72
|
>
|
|
73
73
|
{/* COUPONS Heading */}
|
|
74
|
-
<Heading className="text-lg font-bold text-
|
|
74
|
+
<Heading className="text-lg font-bold text-[var(--sf-color-text)] mb-4 uppercase">
|
|
75
75
|
COUPONS
|
|
76
76
|
</Heading>
|
|
77
77
|
|
|
@@ -86,15 +86,15 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
86
86
|
<div className="flex items-center gap-3">
|
|
87
87
|
{/* Percentage Icon - White circle with black % symbol */}
|
|
88
88
|
<div className="w-10 h-10 rounded-full bg-white flex items-center justify-center flex-shrink-0 shadow-sm">
|
|
89
|
-
<span className="text-
|
|
89
|
+
<span className="text-[var(--sf-color-text)] font-bold text-xl">%</span>
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
{/* Text Content */}
|
|
93
93
|
<div className="flex flex-col items-start">
|
|
94
|
-
<span className="font-bold text-base text-
|
|
94
|
+
<span className="font-bold text-base text-[var(--sf-color-text)]">
|
|
95
95
|
Apply Coupons
|
|
96
96
|
</span>
|
|
97
|
-
<span className="text-sm text-
|
|
97
|
+
<span className="text-sm text-[var(--sf-color-text-muted)]">
|
|
98
98
|
View all offers
|
|
99
99
|
</span>
|
|
100
100
|
</div>
|
|
@@ -102,7 +102,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
102
102
|
|
|
103
103
|
{/* Chevron Icon */}
|
|
104
104
|
<svg
|
|
105
|
-
className="w-5 h-5 text-
|
|
105
|
+
className="w-5 h-5 text-[var(--sf-color-text)]"
|
|
106
106
|
fill="none"
|
|
107
107
|
stroke="currentColor"
|
|
108
108
|
viewBox="0 0 24 24"
|
|
@@ -131,7 +131,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
131
131
|
/>
|
|
132
132
|
<SubmitButton
|
|
133
133
|
variant="secondary"
|
|
134
|
-
className="!bg-[
|
|
134
|
+
className="!bg-[var(--sf-btn-primary)] hover:!bg-[var(--sf-btn-primary-hover)] text-white border-0"
|
|
135
135
|
data-testid="discount-apply-button"
|
|
136
136
|
>
|
|
137
137
|
Apply
|
|
@@ -157,7 +157,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
157
157
|
{/* Applied Promotions - Clean Card Redesign */}
|
|
158
158
|
{promotions.length > 0 && (
|
|
159
159
|
<div className="w-full flex flex-col mt-6 pt-4 border-t border-gray-100">
|
|
160
|
-
<Heading className="text-sm font-bold mb-3 text-
|
|
160
|
+
<Heading className="text-sm font-bold mb-3 text-[var(--sf-color-text)] uppercase tracking-tight">
|
|
161
161
|
Promotion(s) applied:
|
|
162
162
|
</Heading>
|
|
163
163
|
|
|
@@ -166,12 +166,12 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
166
166
|
return (
|
|
167
167
|
<div
|
|
168
168
|
key={promotion.id}
|
|
169
|
-
className="flex items-center justify-between w-full p-3 rounded-xl border border-purple-100 bg-
|
|
169
|
+
className="flex items-center justify-between w-full p-3 rounded-xl border border-purple-100 bg-[var(--sf-color-primary)]/10 transition-all hover:bg-[var(--sf-color-primary)]/10 group"
|
|
170
170
|
data-testid="discount-row"
|
|
171
171
|
>
|
|
172
172
|
<div className="flex items-center gap-x-3 overflow-hidden">
|
|
173
173
|
{/* Tag Icon */}
|
|
174
|
-
<div className="flex-shrink-0 text-[
|
|
174
|
+
<div className="flex-shrink-0 text-[var(--sf-btn-primary)]">
|
|
175
175
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
176
176
|
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
|
|
177
177
|
<line x1="7" y1="7" x2="7.01" y2="7"></line>
|
|
@@ -179,7 +179,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
179
179
|
</div>
|
|
180
180
|
|
|
181
181
|
<div className="flex flex-col overflow-hidden">
|
|
182
|
-
<span className="font-bold truncate text-sm uppercase tracking-wider" style={{ color: '
|
|
182
|
+
<span className="font-bold truncate text-sm uppercase tracking-wider" style={{ color: 'var(--sf-btn-primary)' }} data-testid="discount-code">
|
|
183
183
|
{promotion.code}
|
|
184
184
|
</span>
|
|
185
185
|
{promotion.application_method?.value !== undefined && (
|
|
@@ -197,7 +197,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
197
197
|
|
|
198
198
|
{!promotion.is_automatic && (
|
|
199
199
|
<button
|
|
200
|
-
className="p-2 text-
|
|
200
|
+
className="p-2 text-[var(--sf-color-text-muted)] hover:text-red-500 hover:bg-red-50 rounded-full transition-all"
|
|
201
201
|
onClick={() => {
|
|
202
202
|
if (!promotion.code) return
|
|
203
203
|
removePromotionCode(promotion.code)
|
|
@@ -1,134 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import React from "react"
|
|
4
|
-
|
|
5
|
-
type FilterCheckboxGroupProps = {
|
|
6
|
-
title: string
|
|
7
|
-
items: { value: string; label: string }[]
|
|
8
|
-
values: string[]
|
|
9
|
-
handleChange: (value: string) => void
|
|
10
|
-
isColor?: boolean
|
|
11
|
-
'data-testid'?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const FilterCheckboxGroup = ({
|
|
15
|
-
title,
|
|
16
|
-
items,
|
|
17
|
-
values,
|
|
18
|
-
handleChange,
|
|
19
|
-
isColor,
|
|
20
|
-
'data-testid': dataTestId,
|
|
21
|
-
}: FilterCheckboxGroupProps) => {
|
|
22
|
-
const [isOpen, setIsOpen] = React.useState(true)
|
|
23
|
-
const [showAll, setShowAll] = React.useState(false)
|
|
24
|
-
|
|
25
|
-
// Optimistic local state — reflects clicks instantly without waiting for server
|
|
26
|
-
const [optimisticValues, setOptimisticValues] = React.useState<string[]>(values)
|
|
27
|
-
|
|
28
|
-
// Sync back when server props update (after navigation completes)
|
|
29
|
-
React.useEffect(() => {
|
|
30
|
-
setOptimisticValues(values)
|
|
31
|
-
}, [values])
|
|
32
|
-
|
|
33
|
-
const initialItemsCount = 6
|
|
34
|
-
const hasMore = items?.length > initialItemsCount
|
|
35
|
-
|
|
36
|
-
// Auto-expand if a selected item is hidden or if any item is selected
|
|
37
|
-
React.useEffect(() => {
|
|
38
|
-
if (values && values.length > 0) {
|
|
39
|
-
setIsOpen(true)
|
|
40
|
-
if (hasMore && items) {
|
|
41
|
-
const hiddenItems = items.slice(initialItemsCount)
|
|
42
|
-
const isAnyHiddenItemSelected = hiddenItems.some(item => values.includes(item.value))
|
|
43
|
-
if (isAnyHiddenItemSelected) {
|
|
44
|
-
setShowAll(true)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}, [values, items, hasMore])
|
|
49
|
-
|
|
50
|
-
const handleOptimisticChange = (value: string) => {
|
|
51
|
-
// Immediately toggle locally — no flicker
|
|
52
|
-
setOptimisticValues((prev) =>
|
|
53
|
-
prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
|
|
54
|
-
)
|
|
55
|
-
// Trigger actual URL navigation in background
|
|
56
|
-
handleChange(value)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const displayedItems = showAll ? items : items?.slice(0, initialItemsCount)
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div className="flex flex-col border-b border-gray-100 pb-4 last:border-0 last:pb-0">
|
|
63
|
-
<button
|
|
64
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
65
|
-
className="flex items-center justify-between w-full group transition-all py-1"
|
|
66
|
-
>
|
|
67
|
-
<Heading
|
|
68
|
-
level="h3"
|
|
69
|
-
className="text-sm font-bold text-gray-900 uppercase tracking-wider group-hover:text-[#8B5AB1] transition-colors"
|
|
70
|
-
>
|
|
71
|
-
{title}
|
|
72
|
-
</Heading>
|
|
73
|
-
<span
|
|
74
|
-
className={`transform transition-transform duration-300 text-gray-400 group-hover:text-[#8B5AB1] ${isOpen ? "" : "-rotate-180"}`}
|
|
75
|
-
>
|
|
76
|
-
<svg
|
|
77
|
-
width="18"
|
|
78
|
-
height="18"
|
|
79
|
-
viewBox="0 0 24 24"
|
|
80
|
-
fill="none"
|
|
81
|
-
stroke="currentColor"
|
|
82
|
-
strokeWidth="2.5"
|
|
83
|
-
strokeLinecap="round"
|
|
84
|
-
strokeLinejoin="round"
|
|
85
|
-
>
|
|
86
|
-
<polyline points="18 15 12 9 6 15"></polyline>
|
|
87
|
-
</svg>
|
|
88
|
-
</span>
|
|
89
|
-
</button>
|
|
90
|
-
|
|
91
|
-
<div
|
|
92
|
-
className={`flex flex-col gap-y-1 transition-all duration-300 ease-in-out ${isOpen ? "mt-3 opacity-100 px-0.5" : "max-h-0 opacity-0 invisible overflow-hidden"}`}
|
|
93
|
-
style={isOpen ? { maxHeight: showAll ? '1000px' : '300px' } : {}}
|
|
94
|
-
>
|
|
95
|
-
{displayedItems?.map((i) => (
|
|
96
|
-
<CheckboxWithLabel
|
|
97
|
-
key={i.value}
|
|
98
|
-
label={i.label}
|
|
99
|
-
color={isColor ? i.value : undefined}
|
|
100
|
-
checked={optimisticValues.includes(i.value)}
|
|
101
|
-
onChange={() => handleOptimisticChange(i.value)}
|
|
102
|
-
data-testid={dataTestId}
|
|
103
|
-
data-ga-event="filter_checkbox_click"
|
|
104
|
-
data-ga-label={`${title} - ${i.label}`}
|
|
105
|
-
/>
|
|
106
|
-
))}
|
|
107
|
-
|
|
108
|
-
{hasMore && isOpen && (
|
|
109
|
-
<button
|
|
110
|
-
onClick={() => setShowAll(!showAll)}
|
|
111
|
-
className="text-xs font-bold text-[#8B5AB1] hover:underline mt-2 flex items-center gap-1 uppercase tracking-tight"
|
|
112
|
-
>
|
|
113
|
-
{showAll ? "View Less" : `View More (${items.length - initialItemsCount})`}
|
|
114
|
-
<svg
|
|
115
|
-
width="12"
|
|
116
|
-
height="12"
|
|
117
|
-
viewBox="0 0 24 24"
|
|
118
|
-
fill="none"
|
|
119
|
-
stroke="currentColor"
|
|
120
|
-
strokeWidth="3"
|
|
121
|
-
strokeLinecap="round"
|
|
122
|
-
strokeLinejoin="round"
|
|
123
|
-
className={`transform transition-transform ${showAll ? "rotate-180" : ""}`}
|
|
124
|
-
>
|
|
125
|
-
<polyline points="6 9 12 15 18 9"></polyline>
|
|
126
|
-
</svg>
|
|
127
|
-
</button>
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export default FilterCheckboxGroup
|
|
1
|
+
/** @deprecated Import from `medusa-ui-product/store/components/filter-checkbox-group` */
|
|
2
|
+
export { default } from "medusa-ui-product/store/components/filter-checkbox-group"
|